﻿// -----------------------------------------------------------------------
// <copyright file="Table.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

using System.Data.Common;
using System.Data.Odbc;

namespace Centido.Core
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;

    /// <summary>
    ///   A user-defined database table.
    /// </summary>
    /// <remarks>
    /// <para>
    ///   This class provides a set of methods for defining the structure of a user-defined table,
    ///   creating table indexes, configuring security permissions and manipulating the data inside the table.
    ///   Executing some of the methods requires having the appropriate security permissions.
    /// </para>
    /// </remarks>
    /// <example>
    ///   The following example shows how to create a user-defined table
    ///   and define its structure.
    /// <code>
    ///     using System;
    ///     using Centido.Core;
    /// 
    ///     class Sample
    ///     {
    ///         static void Main()
    ///         {
    ///             var domainConfig = new DomainConfiguration()
    ///             {
    ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
    ///                 DbProviderName = "MSSQLSERVER"
    ///             };
    ///         
    ///             var domain = Domain.Build(domainConfig);
    ///         
    ///             domain.DeleteAll();
    ///             
    ///             //Adding a new table
    ///             var table = domain.AddTable("students");
    ///             
    ///             //Adding some table columns
    ///             var nameColumn = table.AddColumn("name", ColumnType.String) as StringColumn;
    ///             nameColumn.IsNotNull = true;
    ///             nameColumn.IsNotEmpty = true;
    ///             nameColumn.MaxLength = 64;
    /// 
    ///             var birthdayColumn = table.AddColumn("birthday", ColumnType.DateTime) as DateTimeColumn;
    ///             birthdayColumn.IsNotNull = true;
    ///         }
    ///     }
    /// </code>
    ///   The following example shows how to populate the table built previously with some data.
    /// <code>
    ///     using System;
    ///     using Centido.Core;
    /// 
    ///     class Sample
    ///     {
    ///         static void Main()
    ///         {
    ///             var domainConfig = new DomainConfiguration()
    ///             {
    ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
    ///                 DbProviderName = "MSSQLSERVER"
    ///             };
    ///         
    ///             var domain = Domain.Build(domainConfig);
    ///             
    ///             var table = domain.GetTable("students");
    /// 
    ///             if (table != null)
    ///             {
    ///                 //Granting all necessary permissions
    ///                 if (!domain.CurrentUser.CanDo(table, DataAction.Insert))
    ///                     table.Grant(DataAction.Insert);
    ///
    ///                 if (!domain.CurrentUser.CanDo(table, DataAction.Select))
    ///                     table.Grant(DataAction.Select);
    ///             
    ///                 //Inserting a student
    ///                 var id = table.InsertRow(table.GetColumn("name").Value("John Smith"),
    ///                     table.GetColumn("birthday").Value(DateTime.Now.AddYears(-25)));
    /// 
    ///                 //Reading inserted values
    ///                 var insertedName = table.GetColumn("name").EmptyValue();
    ///                 var insertedBirthday = table.GetColumn("birthday").EmptyValue();
    /// 
    ///                 table.ReadRow(id, insertedName, insertedBirthday);
    /// 
    ///                 Console.WriteLine("Student name = {0}, birthay = {1}",
    ///                     DbValue.GetString(insertedName),
    ///                     DbValue.GetDateTime(insertedBirthday));
    ///             }
    ///        }
    ///     }
    /// </code>
    /// </example>
    public sealed class Table : IRenamable, IRemovable, IIdentifiedById, IIdentifiedByName, IRefreshable
    {
        /// <summary>
        ///   Parent domain.
        /// </summary>
        private readonly Domain _domain;

        /// <summary>
        ///   System columns collection.
        /// </summary>
        private readonly SystemColumns _systemColumns;

        /// <summary>
        ///   The table name.
        /// </summary>
        private string _name;

        /// <summary>
        ///   The table display name.
        /// </summary>
        private string _displayName;

        /// <summary>
        ///   The table description;
        /// </summary>
        private string _description;

        /// <summary>
        ///   The dictionary that eases the access to table columns by their id.
        /// </summary>
        private readonly Dictionary<Guid, Column> _columnsById = new Dictionary<Guid, Column>();

        /// <summary>
        ///   The dictionary that eases the access to table columns by their name.
        /// </summary>
        private readonly Dictionary<string, Column> _columnsByName = new Dictionary<string, Column>();

        /// <summary>
        ///   Initializes a new instance of the <see cref = "Centido.Core.Table" /> class
        ///   for the specified table id and parent domain.
        /// </summary>
        /// <param name = "tableId">The specified table id.</param>
        /// <param name = "domain">The parent domain.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="domain"/> is null.</exception>
        internal Table(Guid tableId, Domain domain)
        {
            if (domain == null)
                throw new ArgumentNullException("domain");

            Id = tableId;
            _domain = domain;

            _systemColumns = new SystemColumns(this);
        }

        /// <summary>
        ///   Initializes a new instance of the <see cref="Centido.Core.Table"/> class
        /// for the specified table id, name, display name, display collection name,
        /// description and parent domain..
        /// </summary>
        /// <param name="tableId">The table id.</param>
        /// <param name="domain">The parent domain.</param>
        /// <param name="name">The table name.</param>
        /// <param name="displayName">The table display name.</param>
        /// <param name="description">The table description.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="domain"/> is null.</exception>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="name"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="name"/> is an empty string.</exception>
        internal Table(Guid tableId, Domain domain,
            string name, string displayName, string description) : this(tableId, domain)
        {
            if (domain == null)
                throw new ArgumentNullException("domain");
            if (name == null)
                throw new ArgumentNullException("name");
            if (name.Trim().Length == 0)
                throw new ArgumentException("The table name cannot be empty.");

            _name = name.Trim().ToLower();
            _displayName = displayName;
            _description = description;
        }

        /// <summary>
        ///   Gets the fully qualified table name that includes the schema name.
        /// </summary>
        /// <example>
        ///   The following example shows how to use this property when building custom queries.    
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        /// 
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");    
        /// 
        ///             //Creating a new integer column
        ///             var column = table.AddColumn("my_column", ColumnType.Integer);
        /// 
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        /// 
        ///             //Inserting 100 rows
        ///             Random rand = new Random();
        /// 
        ///             for(int i = 0; i &lt; 100; i++)
        ///                 table.InsertRow(column.Value(rand.Next() % 1000));
        /// 
        ///             var columnName = column.Name;
        /// 
        ///             //Searching for the inserted values that are lower than 500
        ///             domain.DbAccess.ExecuteReader(cmd =>
        ///             {
        ///                 cmd.CommandText = string.Format("select id, {0} from {1} where {2} &lt; {3}",
        ///                     column.Name,
        ///                     table.FullName,
        ///                     column.Name,
        ///                     domain.DbAccess.FormatMarker("max_value"));  
        ///                 
        ///                 cmd.Parameters.Add(domain.DbAccess.CreateParameter("max_value", OdbcType.Int, 500));
        ///             }, reader =>
        ///             {
        ///                 Console.WriteLine("Row id = {0}, column value = {1}",
        ///                     DbValue.GetGuid(reader["id"]),
        ///                     DbValue.GetInteger(reader[columnName]));
        ///             });
        ///         }
        ///     }
        /// </code>
        /// </example>
        public string FullName
        {
            get { return Domain.GetObjectFullName(Name); }
        }

        /// <summary>
        ///   Gets the fully qualified internal table table name that includes the schema name.
        /// </summary>
        /// <remarks>
        ///   Internal table is used for storing all data including the rows that were soft deleted.
        ///   Only users that have the permission to modify the schema have access to internal tables.
        /// </remarks>
        /// <example>
        ///   The following example shows how to restore soft deleted rows changing the IsActive field in the internal table.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        /// 
        ///             domain.DeleteAll();
        /// 
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        /// 
        ///             //Inserting two rows
        ///             table.InsertRow();
        ///             table.InsertRow();
        /// 
        ///             Console.WriteLine("Row count = {0}", table.RowCount);
        ///             
        ///             //Deleting the rows
        ///             table.DeleteAllRows();
        /// 
        ///             Console.WriteLine("Deleting the rows");
        /// 
        ///             Console.WriteLine("Row count = {0}", table.RowCount);
        /// 
        ///             //Changing the IsActive field in the internal table
        ///             domain.DbAccess.ExecuteNonQuery(cmd =>
        ///             {
        ///                 cmd.CommandText = string.Format("update {0} set {1} = 1",
        ///                     table.FullInternalName,
        ///                     table.SystemColumns.IsActive.Name);
        ///             });
        /// 
        ///             Console.WriteLine("Restoring the rows");
        /// 
        ///             Console.WriteLine("Row count = {0}", table.RowCount);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public string FullInternalName
        {
            get { return Domain.GetObjectFullName(string.Format("{0}{1}", Db.Tables.Prefix, Name)); }
        }

        /// <summary>
        ///   Gets the domain used to connect to the database.
        /// </summary>
        public Domain Domain
        {
            get { return _domain; }
        }

        /// <summary>
        ///   Gets or sets the table description.
        /// </summary>
        /// <remarks>
        /// <para>The current user has to have the permission to change the schema in order to set this property.</para>
        /// <para>The value of this field is trimmed when it is not null.</para>  
        /// </remarks>
        /// <value>The string that represents the table description or null.</value>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to set this property.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when trying to read this property on a deleted table.</exception>
        /// <example>
        ///   The following example shows how to set the description of the table.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        /// 
        ///             //Creating a new table
        ///             var table = domain.AddTable("students");
        ///             
        ///             //Setting the table description
        ///             table.Description = "This table stores a student list.";
        /// 
        ///             //Reading the table description
        ///             Console.WriteLine("Table name = {0}, description = {1}",
        ///                 table.Name,
        ///                 table.Description);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public string Description
        {
            get { return _description ?? (_description = DbValue.GetString(ReadProperty(
                Db.Tables.TableList.Col.Description))); }
            set
            {
                if (value != null)
                    value = value.Trim();

                if (!string.IsNullOrEmpty(_description) && _description == value) return;

                WriteProperty(Db.Tables.TableList.Col.Description, value, OdbcType.NVarChar);

                _description = value;
            }
        }

        /// <summary>
        ///   Gets or sets the display name of the table in a singular form.
        /// </summary>
        /// <remarks>
        /// <para>The current user has to have the permission to change the schema in order to set this property.</para>
        /// <para>The value of this field is trimmed when it is not null.</para>
        /// </remarks>
        /// <value>The string that represents the table display name in singular form or null.</value>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to set this property.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when trying to read this property on a deleted table.</exception>
        public string DisplayName
        {
            get { return _displayName ?? (_displayName = DbValue.GetString(
                ReadProperty(Db.Tables.TableList.Col.DisplayName))); }
            set
            {
                if (value != null)
                    value = value.Trim();

                if (!string.IsNullOrEmpty(_displayName) && _displayName == value) return;

                WriteProperty(Db.Tables.TableList.Col.DisplayName, value, OdbcType.NVarChar);

                _displayName = value;
            }
        }

        /// <summary>
        ///   Gets the number of rows in the table.   
        /// </summary>
        /// <remarks>
        /// <para>The current user has to have the select permission on this table in order to read this property.</para>
        /// <para>The number returned by this property doesn't include the rows that were soft deleted.</para>
        /// </remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no select permission on the table.</exception>
        /// <example>
        ///   The following example shows how to get the number of rows in a user-defined table.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll(); 
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting all necessary permissions
        ///             table.GrantAll();   
        /// 
        ///             //Inserting 10 rows
        ///             for (int i = 0; i &lt; 10; i++)
        ///                 table.InsertRow();
        /// 
        ///             Console.WriteLine("Row count = {0}", table.RowCount);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public long RowCount
        {
            get
            {
                long rowCount = 0;

                _domain.DbAccess.ExecuteScalar(
                    cmd => { cmd.CommandText = string.Format("select count(*) from {0}", FullName); },
                    result => rowCount = Convert.ToInt64(result));

                return rowCount;
            }
        }

        /// <summary>
        ///   Gets the number of rows that were soft deleted.   
        /// </summary>
        /// <remarks>
        /// <para>The current user has to have the permission to change the schema in order to read this property.</para>
        /// </remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to change the schema.</exception>
        /// <example>
        ///   The following example shows how to get the number of soft deleted rows in a user-defined table.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        /// 
        ///             //Inserting a row
        ///             var id = table.InsertRow();
        /// 
        ///             Console.WriteLine("Row count = {0} (soft deleted rows = {1})",
        ///                 table.RowCount,
        ///                 table.DeletedRowCount);
        ///             
        ///             //Deleting the row
        ///             table.DeleteRow(id);
        /// 
        ///             Console.WriteLine("Deleting the row.");
        /// 
        ///             Console.WriteLine("Row count = {0} (soft deleted rows = {1})",
        ///                 table.RowCount,
        ///                 table.DeletedRowCount);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public long DeletedRowCount
        {
            get
            {
                long deletedRowCount = 0;

                _domain.DbAccess.ExecuteScalar(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select count(*) from {0} where {1}=0", FullInternalName, Db.Tables.UserTable.Col.IsActive);
                    },
                    result =>
                    {
                        var rowCount = DbValue.GetBigInteger(result);

                        if (rowCount != null)
                            deletedRowCount = rowCount.Value;
                    });

                return deletedRowCount;
            }
        }

        /// <summary>
        ///   Gets the number of rows in the internal table.
        /// </summary>
        /// <remarks>
        /// <para>The internal table stores all the data including the soft deleted rows.</para>
        /// <para>The current user has to have the permission to change the schema in order to read this property.</para>
        /// </remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to change the schema.</exception>
        /// <example>
        ///   The following example shows how to get the number rows in the internal table.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        /// 
        ///             //Inserting a row
        ///             var id = table.InsertRow();
        /// 
        ///             Console.WriteLine("Row count = {0} (internal rows = {1})",
        ///                 table.RowCount,
        ///                 table.InternalRowCount);
        ///             
        ///             //Deleting the row
        ///             table.DeleteRow(id);
        /// 
        ///             Console.WriteLine("Deleting the row.");
        /// 
        ///             Console.WriteLine("Row count = {0} (internal rows = {1})",
        ///                 table.RowCount,
        ///                 table.InternalRowCount);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public long InternalRowCount
        {
            get
            {
                long internalRowCount = 0;

                _domain.DbAccess.ExecuteScalar(
                    cmd => { cmd.CommandText = string.Format("select count(*) from {0}", FullInternalName); },
                    result =>
                    {
                        var rowCount = DbValue.GetBigInteger(result);

                        if (rowCount != null)
                            internalRowCount = rowCount.Value;
                    });

                return internalRowCount;
            }
        }

        /// <summary>
        ///   Gets the list of the table columns.
        /// </summary>
        /// <example>
        ///   The following example creates a new table and shows the table colums.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Adding a new column
        ///             table.AddColumn("my_column", ColumnType.Integer);
        /// 
        ///             //Enumerating all table columns
        ///             foreach(var column in table.Columns)
        ///             {
        ///                 Console.WriteLine("Column name = {0}, Is system column = {1}",
        ///                     column.Name,
        ///                     column.IsSystem);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<Column> Columns
        {
            get
            {
                // Clearing the column cache
                ClearColumnCache();

                var columnList = new List<Column>();

                Domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0},{1},{2},{3},{4},{5},{6},{7} from {8} where {9}=?",
                            Db.Views.ColumnList.Col.ColumnId,
                            Db.Views.ColumnList.Col.ColumnType,
                            Db.Views.ColumnList.Col.ColumnName,
                            Db.Views.ColumnList.Col.ColumnDisplayName,
                            Db.Views.ColumnList.Col.ColumnDescription,
                            Db.Views.ColumnList.Col.ColumnIsNotNull,
                            Db.Views.ColumnList.Col.ColumnOrderIndex,
                            Db.Views.ColumnList.Col.ColumnCustomizationLevel,
                            Domain.GetObjectFullName(Db.Views.ColumnList.Name),
                            Db.Views.ColumnList.Col.TableId);

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, GetRawId()));
                    },
                    reader =>
                    {
                        var columnId = reader.GetBinary(Db.Views.ColumnList.Col.ColumnId);
                        var columnName = reader.GetString(Db.Views.ColumnList.Col.ColumnName);
                        var displayName = reader.GetString(Db.Views.ColumnList.Col.ColumnDisplayName);
                        var description = reader.GetString(Db.Views.ColumnList.Col.ColumnDescription);
                        var isNotNull = reader.GetBoolean(Db.Views.ColumnList.Col.ColumnIsNotNull).GetValueOrDefault();
                        var orderIndex = reader.GetInteger(Db.Views.ColumnList.Col.ColumnOrderIndex).GetValueOrDefault();
                        var customizationLevel =
                            reader.GetInteger(Db.Views.ColumnList.Col.ColumnCustomizationLevel).GetValueOrDefault();

                        // Column type
                        var columnType = reader.GetString(Db.Views.ColumnList.Col.ColumnType);

                        var column = CreateColumn(columnId, columnType,
                            columnName, displayName, description, isNotNull, orderIndex, customizationLevel);

                        columnList.Add(column);

                        // Saving the column to the cache
                        SaveColumnToCache(column);
                    });

                return columnList;
            }
        }

        /// <summary>
        ///   Gets the container that stores references to the system columns of the current table.
        /// </summary>
        /// <remarks>The system column is a column that is created in every user-defined table
        /// and cannot be deleted or altered.</remarks>
        /// <value>An instance of the <see cref="Centido.Core.SystemColumns"/> class.</value>
        /// <example>
        ///   The following example shows how to change the owner of the inserted row.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Adding a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        /// 
        ///             //Inserting a row
        ///             var id = table.InsertRow();
        /// 
        ///             //Reading the row owner
        ///             var rowOwner = table.SystemColumns.Owner.EmptyValue();
        /// 
        ///             table.ReadRow(id, rowOwner);
        /// 
        ///             Console.WriteLine("Row owner = {0}", DbValue.GetString(rowOwner));  
        /// 
        ///             //Changing the row owner
        ///             Console.WriteLine("Changing the row owner.");
        /// 
        ///             table.UpdateRow(id, table.SystemColumns.Owner.Value("new_owner"));
        /// 
        ///             //Reading the row owner
        ///             rowOwner = table.SystemColumns.Owner.EmptyValue();
        /// 
        ///             table.ReadRow(id, rowOwner);
        /// 
        ///             Console.WriteLine("Row owner = {0}", DbValue.GetString(rowOwner));
        ///         }
        ///     }
        /// </code>
        /// </example>
        public SystemColumns SystemColumns
        {
            get { return _systemColumns; }
        }

        /// <summary>
        ///   Gets the list of the reference columns of the table.
        /// </summary>
        public ICollection<RefColumn> RefColumns
        {
            get { return Columns.Where(c => c is RefColumn).Cast<RefColumn>().ToList(); }
        }

        /// <summary>
        ///   Gets the list of reference targets from the current table.
        /// </summary>
        /// <example>
        ///   The following example shows how to get the list of references from the current table.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Adding the first table
        ///             var firstTable = domain.AddTable("first_table");
        /// 
        ///             //Adding the second table
        ///             var secondTable = domain.AddTable("second_table");
        /// 
        ///             //Adding the third table
        ///             var thirdTable = domain.AddTable("third_table");
        /// 
        ///             //Adding reference column
        ///             var refColumn = thirdTable.AddColumn("my_ref", ColumnType.Reference)
        ///                 as RefColumn;   
        /// 
        ///             refColumn.AddTarget(firstTable);
        ///             refColumn.AddTarget(secondTable);
        /// 
        ///             //Enumerating references from the third table
        ///             foreach(var refTarget in thirdTable.RefsFrom)
        ///             {
        ///                 Console.WriteLine("Reference from the current table to {0}", refTarget.RefTable.Name);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<RefTarget> RefsFrom
        {
            get
            {
                var resultList = new List<RefTarget>();

                foreach (var column in RefColumns)
                {
                    var columnId = column.Id;
                    var columnRawId = column.GetRawId();

                    _domain.DbAccess.ExecuteReader(
                        cmd =>
                        {
                            cmd.CommandText = string.Format(
                                "select {0} from {1} where {2}=?",
                                Db.Tables.ReferenceTargetList.Col.ReferencedTableId,
                                Domain.GetObjectFullName(Db.Tables.ReferenceTargetList.Name),
                                Db.Tables.ReferenceTargetList.Col.ColumnId);

                            cmd.Parameters.Add(
                                _domain.DbAccess.CreateParameter("column_id", OdbcType.Binary, columnRawId));
                        },
                        reader =>
                        {
                            var refTableId = DbValue.GetGuid(
                                reader[Db.Tables.ReferenceTargetList.Col.ReferencedTableId]);

                            if (refTableId != null)
                                resultList.Add(
                                    new RefTarget(new RefColumn(columnId, this), new Table(refTableId.Value, Domain)));
                        });
                }

                return resultList;
            }
        }

        /// <summary>
        ///   Gets the list of references to the current table.
        /// </summary>
        /// <example>
        ///   The following example shows how to get the list of references to the current table.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Adding the first table
        ///             var firstTable = domain.AddTable("first_table");
        /// 
        ///             //Adding the second table
        ///             var secondTable = domain.AddTable("second_table");
        /// 
        ///             //Adding the third table
        ///             var thirdTable = domain.AddTable("third_table");
        /// 
        ///             //Adding references column to the first table
        ///             var refColumn3 = thirdTable.AddColumn("my_ref", ColumnType.Reference)
        ///                 as RefColumn; 
        ///             refColumn3.AddTarget(firstTable);
        /// 
        ///             var refColumn2 = secondTable.AddColumn("my_ref", ColumnType.Reference)
        ///                 as RefColumn; 
        ///             refColumn2.AddTarget(firstTable);
        /// 
        ///             //Enumerating references to the first table
        ///             foreach(var refTarget in firstTable.RefsTo)
        ///             {
        ///                 Console.WriteLine("Reference to the current table from {0}", refTarget.Table.Name);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<RefTarget> RefsTo
        {
            get
            {
                var refColumnIdList = new List<Guid>();

                _domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0} from {1} where {2}=?",
                            Db.Tables.ReferenceTargetList.Col.ColumnId,
                            Domain.GetObjectFullName(Db.Tables.ReferenceTargetList.Name),
                            Db.Tables.ReferenceTargetList.Col.ReferencedTableId);

                        cmd.Parameters.Add(_domain.DbAccess.CreateParameter("ref_table_id", OdbcType.Binary, GetRawId()));
                    },
                    reader =>
                    {
                        var columnId = reader.GetGuid(Db.Tables.ReferenceTargetList.Col.ColumnId);

                        if (columnId != null)
                            refColumnIdList.Add(columnId.Value);
                    });

                return (from columnId in refColumnIdList
                       let table = GetTableByColumnId(columnId)
                       select new RefTarget(new RefColumn(columnId, table), this)).ToList();
            }
        }

        /// <summary>
        ///   Gets the list of table indexes.
        /// </summary>
        /// <example>
        ///   The following example shows the list of default table indexes.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a table
        ///             var table = domain.AddTable("my_table");    
        /// 
        ///             //Enumerating default table indexes
        ///             foreach(var index in table.Indexes)
        ///             {
        ///                 Console.WriteLine("Index id = {0}, unique = {1}", index.Id, index.IsUnique);
        /// 
        ///                 foreach(var column in index.Columns)
        ///                 {
        ///                     Console.WriteLine("\tColumn = {0}", column.Name);
        ///                 }
        ///             }   
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<Index> Indexes
        {
            get
            {
                var resultList = new List<Index>();

                Domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0} from {1} where {2}=?",
                            Db.Tables.IndexList.Col.IndexId,
                            Domain.GetObjectFullName(Db.Tables.IndexList.Name),
                            Db.Tables.IndexList.Col.TableId);

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, GetRawId()));
                    },
                    reader =>
                    {
                        var indexId = reader.GetGuid(Db.Tables.IndexList.Col.IndexId);

                        if (indexId != null)
                            resultList.Add(new Index(indexId.Value, this));
                    });

                return resultList;
            }
        }

        /// <summary>
        ///   Gets the list of the permissions granted on the current table.
        /// </summary>
        /// <remarks>The current user has to have the permission to change security settings in order to read this property.</remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to read this property.</exception>
        /// <example>
        ///   The following example shows the permissions granted when invoking the GrantAll method.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a table
        ///             var table = domain.AddTable("my_table");    
        /// 
        ///             //Invoking the GrantAll method
        ///             table.GrantAll();
        /// 
        ///             //Enumerating the granted permissions
        ///             foreach(var perm in table.Permissions)
        ///                 Console.WriteLine("Data operation = {0}, access level = {1}",
        ///                     perm.DataAction, perm.AccessLevel);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<TablePermission> Permissions
        {
            get
            {
                var resultList = new List<TablePermission>();

                Domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0}, {1} from {2} where {3}=?",
                            Db.Tables.TableAcl.Col.PrincipalName,
                            Db.Tables.TableAcl.Col.DataAction,
                            Domain.GetObjectFullName(Db.Tables.TableAcl.Name),
                            Db.Tables.TableAcl.Col.TableId);

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, GetRawId()));
                    },
                    reader =>
                    {
                        var accessTypeValue = reader.GetInteger(Db.Tables.TableAcl.Col.DataAction)
                            .GetValueOrDefault();

                        var dataAction = (DataAction)accessTypeValue;
                        var principalName = reader.GetString(Db.Tables.TableAcl.Col.PrincipalName);

                        resultList.Add(new TablePermission(this, principalName, dataAction));
                    });

                return resultList;
            }
        }

        /// <summary>
        ///   Gets the list of low level security access rights.
        /// </summary>
        /// <remarks>
        /// <para>The list of security access rigths is formed dynamically based on the granted permissions.
        /// There are two types of security access rights. The first one (<see cref="Centido.Core.TableFullAccessRight"/>)
        /// denotes the right to perform certain operation on the full range of rows.
        /// The second one (<see cref="TableUnitAccessRight"/>) denotes the right
        /// to perform certain operation on the rows that have a specific owner.</para>
        /// <para>The current user has to have the permission to change security settings in order to read this property.</para>
        /// </remarks>
        /// <example>
        ///   The following example shows the list of dynamically formed low level security access rigths
        ///   when granting permission to select rows from the current business unit.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Creating three users
        ///             var firstUser = domain.AddUser("first_user", "P@ssw0rd");
        ///             var secondUser = domain.AddUser("second_user", "P@ssw0rd");
        ///             var thirdUser = domain.AddUser("third_user", "P@ssw0rd");
        /// 
        ///             //Enrolling this users in the same business unit
        ///             var unit = domain.AddUnit("my_unit");
        /// 
        ///             unit.AddUser(firstUser);
        ///             unit.AddUser(secondUser);
        ///             unit.AddUser(thirdUser);
        /// 
        ///             //Granting permission to select rows of the unit to the first user
        ///             table.Grant(firstUser, DataAction.Select, AccessLevel.Unit);
        /// 
        ///             //Enumerating the security access rights
        ///             foreach(var accessRight in table.AccessRights)
        ///             {
        ///                 if (accessRight is TableAccessRight)
        ///                 {
        ///                     var tar = accessRight as TableAccessRight;
        /// 
        ///                     Console.WriteLine("User = {0}, data action = {1}, owner = {2}",
        ///                         tar.User.Name, tar.DataAction, tar.Owner.Name);
        ///                 }
        ///                 else if (accessRight is TableFullAccessRight)
        ///                 {
        ///                     var tfar = accessRight as TableFullAccessRight;
        /// 
        ///                     Console.WriteLine("User = {0}, data action = {1} (full access)",
        ///                         tfar.User.Name, tfar.DataAction);
        ///                 }
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<ITableAccessRight> AccessRights
        {
            get
            {
                var resultList = new List<ITableAccessRight>();

                // Reading full access rights
                resultList.AddRange(FullAccessRights.OfType<ITableAccessRight>());

                // Reading unit-level access rights
                resultList.AddRange(UnitAccessRights.OfType<ITableAccessRight>());

                // Reading own rows access rights
                resultList.AddRange(OwnRowsAccessRights.OfType<ITableAccessRight>());

                return resultList;
            }
        }


        /// <summary>
        ///   Gets a list of the table full access rights.
        /// </summary>
        /// <remarks>The current user has to have the permission to change security settings in order to read this property.</remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to read this property.</exception>
        public ICollection<TableFullAccessRight> FullAccessRights
        {
            get
            {
                var resultList = new List<TableFullAccessRight>();

                // Reading full access rights
                Domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0}, {1} from {2} where {3}=?",
                            Db.Tables.TableAclFullAccess.Col.UserName,
                            Db.Tables.TableAclFullAccess.Col.DataAction,
                            Domain.GetObjectFullName(Db.Tables.TableAclFullAccess.Name),
                            Db.Tables.TableAclFullAccess.Col.TableId);

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, GetRawId()));
                    },
                    reader =>
                    {
                        var userName = reader.GetString(Db.Tables.TableAclFullAccess.Col.UserName);

                        var dataActionValue = DbValue.GetInteger(reader[Db.Tables.TableAclFullAccess.Col.DataAction])
                            .GetValueOrDefault();

                        var dataAction = (DataAction)dataActionValue;

                        resultList.Add(new TableFullAccessRight(this, new User(userName, Domain), dataAction));
                    });

                return resultList;
            }
        }


        /// <summary>
        ///   Gets a list of the table own rows access rights.
        /// </summary>
        /// <remarks>The current user has to have the permission to change security settings in order to read this property.</remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to read this property.</exception>
        public ICollection<TableOwnRowsAccessRight> OwnRowsAccessRights
        {
            get
            {
                var resultList = new List<TableOwnRowsAccessRight>();

                // Reading own rows access rights
                Domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0}, {1} from {2} where {3}=?",
                            Db.Tables.TableAclOwnRows.Col.UserName,
                            Db.Tables.TableAclOwnRows.Col.DataAction,
                            Domain.GetObjectFullName(Db.Tables.TableAclOwnRows.Name),
                            Db.Tables.TableAclOwnRows.Col.TableId);

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, GetRawId()));
                    },
                    reader =>
                    {
                        var userName = reader.GetString(Db.Tables.TableAclOwnRows.Col.UserName);

                        var dataActionValue = DbValue.GetInteger(reader[Db.Tables.TableAclOwnRows.Col.DataAction])
                            .GetValueOrDefault();

                        var dataAction = (DataAction)dataActionValue;

                        resultList.Add(new TableOwnRowsAccessRight(this, new User(userName, Domain), dataAction));
                    });

                return resultList;
            }
        }


        /// <summary>
        ///   Gets a list of the table unit-level access rights.
        /// </summary>
        /// <remarks>The current user has to have the permission to change security settings in order to read this property.</remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to read this property.</exception>
        public ICollection<TableUnitAccessRight> UnitAccessRights
        {
            get
            {
                var resultList = new List<TableUnitAccessRight>();

                // Reading unit-level access rights
                Domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0}, {1}, {2} from {3} where {4}=?",
                            Db.Tables.TableAclUnitLevelAccess.Col.UserName,
                            Db.Tables.TableAclUnitLevelAccess.Col.UnitId,
                            Db.Tables.TableAclUnitLevelAccess.Col.DataAction,
                            Domain.GetObjectFullName(Db.Tables.TableAclUnitLevelAccess.Name),
                            Db.Tables.TableAclUnitLevelAccess.Col.TableId);

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, GetRawId()));
                    },
                    reader =>
                    {
                        // The user name
                        var userName = reader.GetString(Db.Tables.TableAclUnitLevelAccess.Col.UserName);
                        // The unit id
                        var unitId = reader.GetGuid(Db.Tables.TableAclUnitLevelAccess.Col.UnitId)
                            .GetValueOrDefault();
                        // The data action
                        var dataAction = (DataAction)DbValue.GetInteger(reader[Db.Tables.TableAclUnitLevelAccess.Col.DataAction])
                            .GetValueOrDefault();

                        resultList.Add(new TableUnitAccessRight(this, new User(userName, Domain), unitId, dataAction));
                    });

                return resultList;
            }
        }


        /// <summary>
        ///   Gets the id of the table.
        /// </summary>
        /// <example>
        ///   The following example shows how to add a table with the specified id.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();     
        ///             
        ///             //Creating the table id
        ///             var tableId = Guid.NewGuid();
        /// 
        ///             Console.WriteLine("Id = {0}", tableId);
        /// 
        ///             //Creating a table with specified id
        ///             var table = domain.AddTable(tableId, "my_table");
        /// 
        ///             Console.WriteLine("Table id = {0}", table.Id);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Guid Id { get; private set; }

        /// <summary>
        ///   Checks the existence of the table.
        /// </summary>
        /// <returns>Returns true if the table still exists; otherwise, returns false.</returns>
        public bool Exists()
        {
            var wasFound = false;

            Domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format("select count(*) from {0} where {1}=?",
                        Domain.GetObjectFullName(Db.Tables.TableList.Name),
                        Db.Tables.TableList.Col.TableId);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("id", OdbcType.Binary, GetRawId()));
                },
                result => wasFound = DbValue.GetInteger(result) != 0);

            return wasFound;
        }

        /// <summary>
        ///   Deletes the current table.   
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change the schema in order to execute this function.
        /// </remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when there are references on the current table.</exception>
        /// <example>
        ///   The following example shows how to delete a user-defined table.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        /// 
        ///             //Creating a new table
        ///             domain.AddTable("my_table");
        /// 
        ///             //Enumerating all tables in the domain
        ///             Console.WriteLine("Table list:"); 
        ///             
        ///             foreach(var table in domain.Tables)
        ///                 Console.WriteLine("\tTable = {0}", table.Name);
        /// 
        ///             //Deleting the table
        ///             domain.GetTable("my_table").Delete();
        /// 
        ///             Console.WriteLine("Deleting the table");
        /// 
        ///             //Enumerating all tables in the domain
        ///             Console.WriteLine("Table list:"); 
        ///             
        ///             foreach(var table in domain.Tables)
        ///                 Console.WriteLine("\tTable = {0}", table.Name);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void Delete()
        {
            // Deleting user defined table
            Domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format("{{call {0}(?)}}",
                        Domain.GetObjectFullName(Db.Procedures.DeleteTable));

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_table_id",
                        OdbcType.Binary, GetRawId()));
                });

            // Deleting the table from the domain table cache
            Domain.DeleteTableFromCache(this);
        }

        /// <summary>
        ///   Gets or sets the table name.
        /// </summary>
        /// <remarks>
        /// <para>The current user has to have the permission to change the schema in order to set this property.</para>
        /// <para>The name of the table is always lowered and trimmed.</para>
        /// </remarks>
        /// <exception cref="System.ArgumentNullException">Thrown when trying to set the property to null.</exception>
        /// <exception cref="System.ArgumentException">Thrown when trying to set the property to an empty string.</exception>
        /// <exception cref="System.ArgumentException">Thrown when the new name is not a valid identifier.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to set this property.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when there is a table with the same name.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when trying to read this property on a deleted table.</exception>
        /// <example>
        ///    The following example shows how to rename a user-defined table.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        /// 
        ///             //Creating a new table with the name in upper case
        ///             var table = domain.AddTable("My_Table");
        /// 
        ///             //Showing the name of the table
        ///             Console.WriteLine("Table name = {0}", table.Name);
        /// 
        ///             //Renaming the table with the name in upper case
        ///             table.Name = "My_New_Table";
        /// 
        ///             Console.WriteLine("Renaming the table");
        /// 
        ///             //Showing the name of the table
        ///             Console.WriteLine("Table name = {0}", table.Name);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public string Name
        {
            get { return _name ?? (_name = DbValue.GetString(ReadProperty(Db.Tables.TableList.Col.TableName))); }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");
                if (value.Trim().Length == 0)
                    throw new ArgumentException("Table name cannot be empty.");

                value = value.Trim().ToLower();

                if (_name == value) return;

                DbRule.TestIdentifier(value);

                Domain.DbAccess.ExecuteNonQuery(
                    cmd =>
                    {
                        cmd.CommandText = string.Format("{{call {0}(?,?)}}",
                            Domain.GetObjectFullName(Db.Procedures.RenameTable));

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_table_id",
                            OdbcType.Binary, GetRawId()));
                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_new_table_name",
                            OdbcType.NVarChar, value));
                    });

                // Removing the table with the old name from the table cache
                Domain.DeleteTableFromCache(this);

                _name = value;
            }
        }

        /// <summary>
        ///   Gets a byte array representation of the table id.
        /// </summary>
        /// <remarks>All table ids are stored as byte arrays.</remarks>
        /// <returns>Returns a byte array representation of the table id.</returns>
        /// <example>
        ///   The following example shows how to set the table display name in the metadata directly.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             Console.WriteLine("Setting the table display name directly");
        /// 
        ///             var displayName = "My table";
        /// 
        ///             domain.DbAccess.ExecuteNonQuery(cmd =>
        ///             {
        ///                 cmd.CommandText = string.Format("update {0} set {1}={2} where {3}={4}",
        ///                     domain.GetObjectFullName(Db.Tables.TableList.Name),
        ///                     Db.Tables.TableList.Col.DisplayName,
        ///                     domain.DbAccess.FormatMarker("display_name"),
        ///                     Db.Tables.TableList.Col.TableId,
        ///                     domain.DbAccess.FormatMarker("id"));
        /// 
        ///                 cmd.Parameters.Add(domain.DbAccess.CreateParameter("display_name", OdbcType.NVarChar, displayName));
        ///                 cmd.Parameters.Add(domain.DbAccess.CreateParameter("id", OdbcType.Binary, table.GetRawId()));
        ///             });
        /// 
        ///             Console.WriteLine("Table display name = {0}", table.DisplayName);
        ///         }
        ///     }   
        /// </code>
        /// </example>
        public byte[] GetRawId()
        {
            return Id.ToByteArray();
        }

        /// <summary>
        ///   Retrieves the table column with the specified column id.
        /// </summary>
        /// <remarks>The reference to the newly created table column returned by the method
        /// needs to be converted to a specific column type.</remarks>
        /// <param name = "columnId">The id of the requested table column.</param>
        /// <returns>Returns a table column with the specified column id if there is one; otherwise, returns null.</returns>
        /// <example>
        ///   The following example shows how to retrieve a table column by column id.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Creating a table column
        ///             var column = table.AddColumn("my_column", ColumnType.Integer);
        /// 
        ///             //Retrieving the column by id
        ///             Console.WriteLine("Column name = {0}", table.GetColumn(column.Id).Name);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Column GetColumn(Guid columnId)
        {
            // Trying to get a column from the cache
            var resultColumn = GetColumnFromCache(columnId);

            if (resultColumn != null) return resultColumn;

            Domain.DbAccess.ExecuteReader(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select {0},{1},{2},{3},{4},{5},{6} from {7} where {8}=?",
                        Db.Views.ColumnList.Col.ColumnName,
                        Db.Views.ColumnList.Col.ColumnDisplayName,
                        Db.Views.ColumnList.Col.ColumnDescription,
                        Db.Views.ColumnList.Col.ColumnIsNotNull,
                        Db.Views.ColumnList.Col.ColumnOrderIndex,
                        Db.Views.ColumnList.Col.ColumnCustomizationLevel,
                        Db.Views.ColumnList.Col.ColumnType,
                        Domain.GetObjectFullName(Db.Views.ColumnList.Name),
                        Db.Views.ColumnList.Col.ColumnId);

                    cmd.Parameters.Add(
                        Domain.DbAccess.CreateParameter("column_id", OdbcType.Binary, columnId.ToByteArray()));
                },
                reader =>
                    {
                        var columnName = reader.GetString(Db.Views.ColumnList.Col.ColumnName);
                        var displayName = reader.GetString(Db.Views.ColumnList.Col.ColumnDisplayName);
                        var description = reader.GetString(Db.Views.ColumnList.Col.ColumnDescription);
                        var isNotNull = reader.GetBoolean(Db.Views.ColumnList.Col.ColumnIsNotNull).GetValueOrDefault();
                        var orderIndex = reader.GetInteger(Db.Views.ColumnList.Col.ColumnOrderIndex).GetValueOrDefault();
                        var customizationLevel =
                            reader.GetInteger(Db.Views.ColumnList.Col.ColumnCustomizationLevel).GetValueOrDefault();

                        var columnType = reader.GetString(Db.Views.ColumnList.Col.ColumnType);

                        resultColumn = CreateColumn(columnId.ToByteArray(), columnType,
                                                    columnName, displayName, description, isNotNull, orderIndex,
                                                    customizationLevel);

                        // Saving the column to the cache
                        SaveColumnToCache(resultColumn);
                    });

            return resultColumn;
        }

        /// <summary>
        ///   Retrieves the table column with the specified column name.
        /// </summary>
        /// <remarks>
        /// Table column name is always trimmed and lowered.
        /// The reference to the newly created table column returned by the method
        /// needs to be converted to a specific column type.</remarks>
        /// <param name = "columnName">The name of the requested column.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="columnName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="columnName"/> is an empty string.</exception>
        /// <returns>Returns a table column with the specified column name if there is one; otherwise, returns null.</returns>
        /// <example>
        ///   The following example shows how to retrieve a table column by column name.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Creating a table column
        ///             var column = table.AddColumn("my_column", ColumnType.Integer);
        /// 
        ///             //Retrieving the column by id
        ///             Console.WriteLine("Column id = {0}", table.GetColumn("My_ColumN").Id);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Column GetColumn(string columnName)
        {
            if (columnName == null)
                throw new ArgumentNullException("columnName");
            if (columnName.Trim().Length == 0)
                throw new ArgumentException("Column name cannot be empty.");

            columnName = columnName.Trim().ToLower();

            // Trying to get a column from the cache
            var resultColumn = GetColumnFromCache(columnName);

            if (resultColumn != null) return resultColumn;

            Domain.DbAccess.ExecuteReader(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select {0},{1},{2},{3},{4},{5},{6} from {7} where {8}=? and {9}=?",
                        Db.Views.ColumnList.Col.ColumnId,
                        Db.Views.ColumnList.Col.ColumnDisplayName,
                        Db.Views.ColumnList.Col.ColumnDescription,
                        Db.Views.ColumnList.Col.ColumnIsNotNull,
                        Db.Views.ColumnList.Col.ColumnOrderIndex,
                        Db.Views.ColumnList.Col.ColumnCustomizationLevel,
                        Db.Views.ColumnList.Col.ColumnType,
                        Domain.GetObjectFullName(Db.Views.ColumnList.Name),
                        Db.Views.ColumnList.Col.TableId,
                        Db.Views.ColumnList.Col.ColumnName);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, GetRawId()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("column_name", OdbcType.NVarChar, columnName));
                },
                reader =>
                {
                    var columnId = reader.GetBinary(Db.Views.ColumnList.Col.ColumnId);
                    var displayName = reader.GetString(Db.Views.ColumnList.Col.ColumnDisplayName);
                    var description = reader.GetString(Db.Views.ColumnList.Col.ColumnDescription);
                    var isNotNull = reader.GetBoolean(Db.Views.ColumnList.Col.ColumnIsNotNull).GetValueOrDefault();
                    var orderIndex = reader.GetInteger(Db.Views.ColumnList.Col.ColumnOrderIndex).GetValueOrDefault();
                    var customizationLevel =
                        reader.GetInteger(Db.Views.ColumnList.Col.ColumnCustomizationLevel).GetValueOrDefault();

                    var columnTableName = reader.GetString(Db.Views.ColumnList.Col.ColumnType);

                    resultColumn = CreateColumn(columnId, columnTableName,
                                                columnName, displayName, description, isNotNull, orderIndex,
                                                customizationLevel);

                    // Saving the column to the cache
                    SaveColumnToCache(resultColumn);
                });

            return resultColumn;
        }

        /// <summary>
        ///   Creates a new table column with the specified column id, name and type.   
        /// </summary>
        /// <remarks>
        /// <para>The reference to the newly created table column returned by the method
        /// needs to be converted to a specific column type.</para>
        /// <para>The current user has to have the permission to change the schema in order to execute this function.</para>
        /// </remarks>
        /// <param name = "columnId">The column id.</param>
        /// <param name = "columnName">The column name.</param>
        /// <param name = "columnType">The column type.</param>
        /// <exception cref="System.ArgumentNullException">Thrown when the value of <paramref name="columnName"/> is null.</exception>
        /// <exception cref="System.ArgumentException">Thrown when the value of <paramref name="columnName"/> is an empty string.</exception>
        /// <exception cref="System.ArgumentException">Thrown when the value of <paramref name="columnName"/> is not a valid identifier.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown the current user has no permission to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when there is a column with the same id or the same name in the same user-defined table.</exception>
        /// <returns>Returns the newly created table column.</returns>
        /// <example>
        ///   The following example shows how to create a table column and set its properties.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("students");
        /// 
        ///             //Creating a table column
        ///             var column = table.AddColumn(Guid.NewGuid(), "surname", ColumnType.String)
        ///                 as StringColumn;
        /// 
        ///             //Setting the column properties
        ///             column.DisplayName = "Student surname";
        ///             column.IsNotNull = true;
        ///             column.IsNotEmpty = true;
        ///             column.MaxLength = 64;
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Column AddColumn(Guid columnId, string columnName, ColumnType columnType)
        {
            if (columnName == null)
                throw new ArgumentNullException("columnName");
            if (columnName.Trim().Length == 0)
                throw new ArgumentException("Column name cannot be empty.");

            columnName = columnName.Trim().ToLower();

            DbRule.TestIdentifier(columnName);

            // Function that creates column of that type
            string columnFunction;

            // Result column
            Column resultColumn;

            switch (columnType)
            {
                case ColumnType.String:
                    columnFunction = Domain.GetObjectFullName(Db.Procedures.AddStringColumn);
                    resultColumn = new StringColumn(columnId, this);
                    break;
                case ColumnType.Reference:
                    columnFunction = Domain.GetObjectFullName(Db.Procedures.AddReferenceColumn);
                    resultColumn = new RefColumn(columnId, this);
                    break;
                case ColumnType.BigInteger:
                    columnFunction = Domain.GetObjectFullName(Db.Procedures.AddBigIntColumn);
                    resultColumn = new BigIntColumn(columnId, this);
                    break;
                case ColumnType.Binary:
                    columnFunction = Domain.GetObjectFullName(Db.Procedures.AddBinaryColumn);
                    resultColumn = new BinaryColumn(columnId, this);
                    break;
                case ColumnType.Boolean:
                    columnFunction = Domain.GetObjectFullName(Db.Procedures.AddBoolColumn);
                    resultColumn = new BoolColumn(columnId, this);
                    break;
                case ColumnType.DateTime:
                    columnFunction = Domain.GetObjectFullName(Db.Procedures.AddDateTimeColumn);
                    resultColumn = new DateTimeColumn(columnId, this);
                    break;
                case ColumnType.Enumeration:
                    columnFunction = Domain.GetObjectFullName(Db.Procedures.AddEnumColumn);
                    resultColumn = new EnumColumn(columnId, this);
                    break;
                case ColumnType.Integer:
                    columnFunction = Domain.GetObjectFullName(Db.Procedures.AddIntColumn);
                    resultColumn = new IntColumn(columnId, this);
                    break;
                case ColumnType.Decimal:
                    columnFunction = Domain.GetObjectFullName(Db.Procedures.AddDecimalColumn);
                    resultColumn = new DecimalColumn(columnId, this);
                    break;
                case ColumnType.SmallInteger:
                    columnFunction = Domain.GetObjectFullName(Db.Procedures.AddSmallIntColumn);
                    resultColumn = new SmallIntColumn(columnId, this);
                    break;
                case ColumnType.Guid:
                    columnFunction = Domain.GetObjectFullName(Db.Procedures.AddGuidColumn);
                    resultColumn = new GuidColumn(columnId, this);
                    break;
                case ColumnType.Text:
                    columnFunction = Domain.GetObjectFullName(Db.Procedures.AddTextColumn);
                    resultColumn = new TextColumn(columnId, this);
                    break;
                default:
                    throw new NotSupportedException();
            }

            // Inserting new column
            Domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format("{{call {0}(?,?,?)}}", columnFunction);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_table_id",
                        OdbcType.Binary, GetRawId()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_column_id",
                        OdbcType.Binary, columnId.ToByteArray()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_column_name",
                        OdbcType.NVarChar, columnName));
                });

            // Saving the column to the cache
            SaveColumnToCache(resultColumn);

            return resultColumn;
        }

        /// <summary>
        ///   Creates a new table column with the specified name and type.   
        /// </summary>
        /// <remarks>
        /// <para>The reference to the newly created table column returned by the method
        /// needs to be converted to a specific column type.</para>
        /// <para>The current user has to have the permission to change the schema in order to execute this function.</para>
        /// </remarks>
        /// <param name = "columnName">The column name.</param>
        /// <param name = "columnType">The column type.</param>
        /// <exception cref="System.ArgumentNullException">Thrown when the value of <paramref name="columnName"/> is null.</exception>
        /// <exception cref="System.ArgumentException">Thrown when the value of <paramref name="columnName"/> is an empty string.</exception>
        /// <exception cref="System.ArgumentException">Thrown when the value of <paramref name="columnName"/> is not a valid identifier.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown the current user has no permission to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when there is a column with the same name in the same user-defined table.</exception>
        /// <returns>Returns the newly created table column.</returns>
        /// <example>
        ///   The following example shows how to create a table column and set its properties.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("students");
        /// 
        ///             //Creating a table column
        ///             var column = table.AddColumn("surname", ColumnType.String)
        ///                 as StringColumn;
        /// 
        ///             //Setting the column properties
        ///             column.DisplayName = "Student surname";
        ///             column.IsNotNull = true;
        ///             column.IsNotEmpty = true;
        ///             column.MaxLength = 64;
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Column AddColumn(string columnName, ColumnType columnType)
        {
            return AddColumn(Guid.NewGuid(), columnName, columnType);
        }


        /// <summary>
        ///   Ensures that the table contains a column with the specified id, the specified name and the specified type.
        /// </summary>
        /// <remarks>The current user has to have the permission to change the schema in order to execute this function.</remarks>
        /// <param name="columnId">The column id.</param>
        /// <param name="columnName">The column name.</param>
        /// <param name="columnType">The column type.</param>
        /// <exception cref="System.Data.Common.DbException">Thrown when trying to add a column and
        /// the current user has no permission to change the schema.</exception>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="columnName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="columnName"/> is an empty string.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="columnName"/> is not a valid identifier.</exception>
        /// <exception cref="DbOperationException">Thrown when the table already has a column with the
        /// specified id but different column name.</exception>
        /// <exception cref="DbOperationException">Thrown when the table already has a column with the 
        /// specified id but different column type.</exception>
        /// <exception cref="DbOperationException">Thrown when the table already has a column with the specified name
        /// but different column id.</exception>
        /// <returns>Returns the table column with the specified id, the specified name and the specified type.</returns>
        /// <example>
        ///   The following example shows how to ensure a column with the specified id,
        /// the specified name and the specified type.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("students");
        /// 
        ///             //Creating a table column
        ///             var columnId = Guid.NewGuid();
        ///             table.AddColumn(columnId, "surname", ColumnType.String);
        /// 
        ///             //Ensuring the surname column
        ///             table.EnsureColumn(columnId, "surname", ColumnType.String);
        ///             //Ensuring the age column
        ///             table.EnsureColumn(Guid.NewGuid(), "age", ColumnType.Integer);
        /// 
        ///             foreach(var column in table.Columns)
        ///                 Console.WriteLine("Table column: name = {0}", column.Name);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Column EnsureColumn(Guid columnId, string columnName, ColumnType columnType)
        {
            if (columnName == null)
                throw new ArgumentNullException("columnName");
            if (columnName.Trim().Length == 0)
                throw new ArgumentException("The column name cannot be empty.");

            columnName = columnName.Trim().ToLower();

            DbRule.TestIdentifier(columnName);

            var column = GetColumn(columnId) ?? GetColumn(columnName) ?? AddColumn(columnId, columnName, columnType);

            if (column.Id != columnId)
                throw new DbOperationException("The table already has a column with the specified name but different column id.");
            if (column.Name != columnName)
                throw new DbOperationException("The table already has a column with the specified id but different column name.");
            if (column.ColumnType != columnType)
                throw new DbOperationException("The table already has a column with the specified id but different column type.");

            return column;
        }


        /// <summary>
        ///   Ensures that the table contains a column with the specified name and the specified type.
        /// </summary>
        /// <remarks>The current user has to have the permission to change the schema in order to execute this function.</remarks>
        /// <param name="columnName">The column name.</param>
        /// <param name="columnType">The column type.</param>
        /// <exception cref="System.Data.Common.DbException">Thrown when trying to add a column and
        /// the current user has no permission to change the schema.</exception>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="columnName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="columnName"/> is an empty string.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="columnName"/> is not a valid identifier.</exception>
        /// <exception cref="DbOperationException">Thrown when the table already has a column with the 
        /// specified name but different column type.</exception>
        /// <returns>Returns the table column with the specified name and the specified type.</returns>
        /// <example>
        ///   The following example shows how to ensure that a table has a column with the specified
        /// name and the specified column type.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("students");
        /// 
        ///             //Creating a table column
        ///             table.AddColumn("surname", ColumnType.String);
        /// 
        ///             //Ensuring the surname column
        ///             table.EnsureColumn("surname", ColumnType.String);
        ///             //Ensuring the age column
        ///             table.EnsureColumn("age", ColumnType.Integer);
        /// 
        ///             foreach(var column in table.Columns)
        ///                 Console.WriteLine("Table column: name = {0}", column.Name);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Column EnsureColumn(string columnName, ColumnType columnType)
        {
            if (columnName == null)
                throw new ArgumentNullException("columnName");
            if (columnName.Trim().Length == 0)
                throw new ArgumentException("The column name cannot be empty.");

            DbRule.TestIdentifier(columnName);

            var column = GetColumn(columnName) ?? AddColumn(columnName, columnType);

            if (column.ColumnType != columnType)
                throw new DbOperationException("The table already has a column with the specified name but different column type.");

            return column;
        }

        /// <summary>
        ///   Deletes the table column.
        /// </summary>
        /// <remarks>
        /// <para>Deleting a table column may result in deleting a table index if the column is included in that index.</para>
        /// <para>The current user has to have the permission to change the schema in order to execute this function.</para> 
        /// </remarks>
        /// <param name = "column">The table column.</param>
        /// <exception cref="System.ArgumentNullException">Thrown when the value of <paramref name="column"/> is null.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete a table column.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("students");
        /// 
        ///             //Creating a table column
        ///             var column = table.AddColumn("surname", ColumnType.String);
        ///             
        ///             //Enumerating table columns
        ///             foreach(var c in table.Columns)
        ///             {
        ///                 Console.WriteLine("Column = {0}", c.Name);
        ///             }
        /// 
        ///             //Deleting the column
        ///             table.DeleteColumn(column);
        /// 
        ///             Console.WriteLine("\nDeleting the column.\n");
        /// 
        ///             //Enumerating table columns
        ///             foreach(var c in table.Columns)
        ///             {
        ///                 Console.WriteLine("Column = {0}", c.Name);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteColumn(Column column)
        {
            if (column == null)
                throw new ArgumentNullException("column");

            DeleteColumn(column.Id);
        }

        /// <summary>
        ///   Deletes the table column with specified id.
        /// </summary>
        /// <remarks>
        /// <para>Deleting a table column may result in deleting a table index if the column is included in that index.</para>
        /// <para>The current user has to have the permission to change the schema in order to execute this function.</para>
        /// </remarks>
        /// <param name = "columnId">The table column id.</param>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete a table column.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("students");
        /// 
        ///             //Creating a table column
        ///             var column = table.AddColumn("surname", ColumnType.String);
        ///             
        ///             //Enumerating table columns
        ///             foreach(var c in table.Columns)
        ///             {
        ///                 Console.WriteLine("Column = {0}", c.Name);
        ///             }
        /// 
        ///             //Deleting the column
        ///             table.DeleteColumn(column.Id);
        /// 
        ///             Console.WriteLine("\nDeleting the column.\n");
        /// 
        ///             //Enumerating table columns
        ///             foreach(var c in table.Columns)
        ///             {
        ///                 Console.WriteLine("Column = {0}", c.Name);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteColumn(Guid columnId)
        {
            // Searching for the column with the specified id
            var columnToDelete = GetColumn(columnId);

            if (columnToDelete == null)
                return;

            columnToDelete.Delete();
        }

        /// <summary>
        ///   Deletes the table column with the specified column name.
        /// </summary>
        /// <remarks>
        /// <para>Deleting a table column may result in deleting a table index if the column is included in that index.</para>
        /// <para>The column name is always trimmed and lowered.</para>
        /// <para>The current user has to have the permission to change the schema in order to execute this function.</para>
        /// </remarks>
        /// <param name = "columnName">The table column.</param>
        /// <exception cref="System.ArgumentNullException">Thrown when the value of <paramref name="columnName"/> is null.</exception>
        /// <exception cref="System.ArgumentException">Thrown when the value of <paramref name="columnName"/> is an empty string.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when there is no such a column in the table.</exception>
        /// <example>
        ///   The following example shows how to delete a table column.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("students");
        /// 
        ///             //Creating a table column
        ///             var column = table.AddColumn("surname", ColumnType.String);
        ///             
        ///             //Enumerating table columns
        ///             foreach(var c in table.Columns)
        ///             {
        ///                 Console.WriteLine("Column = {0}", c.Name);
        ///             }
        /// 
        ///             //Deleting the column
        ///             table.DeleteColumn(column.Name);
        /// 
        ///             Console.WriteLine("\nDeleting the column.\n");
        /// 
        ///             //Enumerating table columns
        ///             foreach(var c in table.Columns)
        ///             {
        ///                 Console.WriteLine("Column = {0}", c.Name);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteColumn(string columnName)
        {
            if (columnName == null)
                throw new ArgumentNullException("columnName");
            if (columnName.Trim().Length == 0)
                throw new ArgumentException("Column name cannot be empty.");

            columnName = columnName.Trim().ToLower();

            // Searching for the column with the specified name
            var columnToDelete = GetColumn(columnName);

            if (columnToDelete == null)
                return;

            columnToDelete.Delete();
        }


        /// <summary>
        ///   Creates a new table index with the specified id, index uniqueness and indexed columns.
        /// </summary>
        /// <remarks>
        /// <para>Table indexes help to improve performance of common queries to the table.
        /// All indexes that target queries to the user view of the table should
        /// include the IsActive system column because that column is used to build that view.</para>
        /// <para>If the index is declared to be unique then it guarantees
        /// the uniqueness of all tuples of value of the indexes columns.</para>
        /// <para>Columns of text and binary type cannot participate in table indexes because of their maximum value size.
        /// Columns of string type can participate in table indexes if they have an approprite value in the MaxLength property.</para>
        /// <para>Two indexes are considered to be different if they have the same columns
        /// but different order of columns.</para>
        /// <para>The current user has to have the permission to change the schema in order to execute this function.</para>
        /// </remarks>
        /// <param name = "indexId">The id of the new index.</param>
        /// <param name = "isUnique">True if the new index is unique; otherwise, false.</param>
        /// <param name = "indexedColumns">The list of columns that should be indexed.</param>
        /// <returns>Returns the newly created table index.</returns>
        /// <exception cref="System.Data.Common.DbException">Throw when the current user has no permission to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when there is an index with the same id.</exception>
        /// <exception cref="System.ArgumentException">Thrown when the value of <paramref name="indexedColumns"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the length of <paramref name="indexedColumns"/> is zero.</exception>
        /// <exception cref="ArgumentException">Thrown when there are too many columns in the index.</exception>
        /// <exception cref="ArgumentException">Thrown when of the columns in <paramref name="indexedColumns"/> is null.</exception>
        /// <exception cref="DbException">Thrown when there is an index with the same column set and the same column order in the table.</exception>
        /// <exception cref="DbException">Thrown when the value size of some column in the column list exceeds the maximum value for the current dbms.</exception>
        /// <example>
        ///   The following example shows that two indexes with the same column set but different column order
        /// are considered to be different.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Creating a custom column
        ///             var column = table.AddColumn("my_column", ColumnType.Integer);
        /// 
        ///             //Adding two indexes
        ///             table.AddIndex(Guid.NewGuid(), true,
        ///                 table.SystemColumns.IsActive, column);
        ///             table.AddIndex(Guid.NewGuid(), true,
        ///                 column, table.SystemColumns.IsActive);
        ///             
        ///             //Enumerating all table indexes
        ///             foreach(var index in table.Indexes)
        ///             {
        ///                 Console.WriteLine("Index id = {0}, unique = {1} columns:", index.Id, index.IsUnique);
        /// 
        ///                 foreach(var c in index.Columns)
        ///                     Console.WriteLine("\tColumn name = {0}", c.Name);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Index AddIndex(Guid indexId, bool isUnique, params Column[] indexedColumns)
        {
            // If there is no indexed columns
            if (indexedColumns == null || indexedColumns.Length == 0)
                throw new ArgumentException("There is no index columns specified.");

            // If there are too many columns
            if (indexedColumns.Length > Index.IndexedColumnMaxCount)
                throw new ArgumentException("There are too many index columns.");

            // User column ids string
            var userColumnIdList = new List<byte>();

            foreach (var column in indexedColumns)
            {
                if (column != null)
                    userColumnIdList.AddRange(column.GetRawId());
                else
                    throw new ArgumentException("One of the columns in the list is null.");
            }

            Domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format("{{call {0}(?,?,?,?)}}",
                        Domain.GetObjectFullName(Db.Procedures.AddIndex));

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_index_id",
                        OdbcType.Binary, indexId.ToByteArray()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_table_id",
                        OdbcType.Binary, GetRawId()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_is_unique",
                        OdbcType.Decimal, isUnique ? 1 : 0));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_column_id_list",
                        OdbcType.Binary, userColumnIdList.ToArray()));
                });

            return new Index(indexId, this);
        }

        /// <summary>
        ///   Creates a new non-unique table index with the specified index id and indexed columns.
        /// </summary>
        /// <remarks>
        /// <para>Table indexes help to improve performance of common queries to the table.
        /// All indexes that target queries to the user view of the table should
        /// include the IsActive system column because that column is used to build that view.</para>
        /// <para>Columns of text and binary type cannot participate in table indexes because of their maximum value size.
        /// Columns of string type can participate in table indexes if they have an approprite value in the MaxLength property.</para>
        /// <para>Two indexes are considered to be different if they have the same columns
        /// but different order of columns.</para>
        /// <para>The current user has to have the permission to change the schema in order to execute this function.</para>
        /// </remarks>
        /// <param name = "indexId">The id of the new index.</param>
        /// <param name = "indexedColumns">The list of columns that should be indexed.</param>
        /// <returns>Returns the newly created table index.</returns>
        /// <exception cref="System.Data.Common.DbException">Throw when the current user has no permission to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when there is an index with the same id.</exception>
        /// <exception cref="System.ArgumentException">Thrown when the value of <paramref name="indexedColumns"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the length of <paramref name="indexedColumns"/> is zero.</exception>
        /// <exception cref="ArgumentException">Thrown when there are too many columns in the index.</exception>
        /// <exception cref="ArgumentException">Thrown when of the columns in <paramref name="indexedColumns"/> is null.</exception>
        /// <exception cref="DbException">Thrown when there is an index with the same column set and the same column order in the table.</exception>
        /// <exception cref="DbException">Thrown when the value size of some column in the column list exceeds the maximum value for the current dbms.</exception>
        /// <example>
        ///   The following example shows that two indexes with the same column set but different column order
        /// are considered to be different.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Creating a custom column
        ///             var column = table.AddColumn("my_column", ColumnType.Integer);
        /// 
        ///             //Adding two indexes
        ///             table.AddIndex(Guid.NewGuid(),
        ///                 table.SystemColumns.IsActive, column);
        ///             table.AddIndex(Guid.NewGuid(),
        ///                 column, table.SystemColumns.IsActive);
        ///             
        ///             //Enumerating all table indexes
        ///             foreach(var index in table.Indexes)
        ///             {
        ///                 Console.WriteLine("Index id = {0}, unique = {1} columns:", index.Id, index.IsUnique);
        /// 
        ///                 foreach(var c in index.Columns)
        ///                     Console.WriteLine("\tColumn name = {0}", c.Name);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Index AddIndex(Guid indexId, params Column[] indexedColumns)
        {
            return AddIndex(indexId, false, indexedColumns);
        }

        /// <summary>
        ///   Creates a new table index with the specfied index uniqueness and indexed columns.
        /// </summary>
        /// <remarks>
        /// <para>Table indexes help to improve performance of common queries to the table.
        /// All indexes that target queries to the user view of the table should
        /// include the IsActive system column because that column is used to build that view.</para>
        /// <para>If the index is declared to be unique then it guarantees
        /// the uniqueness of all tuples of value of the indexes columns.</para>
        /// <para>Columns of text and binary type cannot participate in table indexes because of their maximum value size.
        /// Columns of string type can participate in table indexes if they have an approprite value in the MaxLength property.</para>
        /// <para>Two indexes are considered to be different if they have the same columns
        /// but different order of columns.</para>
        /// <para>The current user has to have the permission to change the schema in order to execute this function.</para>
        /// </remarks>
        /// <param name = "isUnique">True if the new index is unique; otherwise, false.</param>
        /// <param name = "indexedColumns">The list of columns that should be indexed.</param>
        /// <returns>Returns the newly created table index.</returns>
        /// <exception cref="System.Data.Common.DbException">Throw when the current user has no permission to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when there is an index with the same id.</exception>
        /// <exception cref="System.ArgumentException">Thrown when the value of <paramref name="indexedColumns"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the length of <paramref name="indexedColumns"/> is zero.</exception>
        /// <exception cref="ArgumentException">Thrown when there are too many columns in the index.</exception>
        /// <exception cref="ArgumentException">Thrown when of the columns in <paramref name="indexedColumns"/> is null.</exception>
        /// <exception cref="DbException">Thrown when there is an index with the same column set and the same column order in the table.</exception>
        /// <exception cref="DbException">Thrown when the value size of some column in the column list exceeds the maximum value for the current dbms.</exception>
        /// <example>
        ///   The following example shows that two indexes with the same column set but different column order
        /// are considered to be different.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Creating a custom column
        ///             var column = table.AddColumn("my_column", ColumnType.Integer);
        /// 
        ///             //Adding two indexes
        ///             table.AddIndex(true,
        ///                 table.SystemColumns.IsActive, column);
        ///             table.AddIndex(true,
        ///                 column, table.SystemColumns.IsActive);
        ///             
        ///             //Enumerating all table indexes
        ///             foreach(var index in table.Indexes)
        ///             {
        ///                 Console.WriteLine("Index id = {0}, unique = {1} columns:", index.Id, index.IsUnique);
        /// 
        ///                 foreach(var c in index.Columns)
        ///                     Console.WriteLine("\tColumn name = {0}", c.Name);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public Index AddIndex(bool isUnique, params Column[] indexedColumns)
        {
            return AddIndex(Guid.NewGuid(), isUnique, indexedColumns);
        }

        /// <summary>
        ///   Creates a new non-unique table index with the specified indexed columns.
        /// </summary>
        /// <remarks>
        /// <para>Table indexes help to improve performance of common queries to the table.
        /// All indexes that target queries to the user view of the table should
        /// include the IsActive system column because that column is used to build that view.</para>
        /// <para>Columns of text and binary type cannot participate in table indexes because of their maximum value size.
        /// Columns of string type can participate in table indexes if they have an approprite value in the MaxLength property.</para>
        /// <para>Two indexes are considered to be different if they have the same columns
        /// but different order of columns.</para>
        /// <para>The current user has to have the permission to change the schema in order to execute this function.</para>
        /// </remarks>
        /// <param name = "indexedColumns">The list of columns that should be indexed.</param>
        /// <returns>Returns the newly created table index.</returns>
        /// <exception cref="System.Data.Common.DbException">Throw when the current user has no permission to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when there is an index with the same id.</exception>
        /// <exception cref="System.ArgumentException">Thrown when the value of <paramref name="indexedColumns"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the length of <paramref name="indexedColumns"/> is zero.</exception>
        /// <exception cref="ArgumentException">Thrown when there are too many columns in the index.</exception>
        /// <exception cref="ArgumentException">Thrown when of the columns in <paramref name="indexedColumns"/> is null.</exception>
        /// <exception cref="DbException">Thrown when there is an index with the same column set and the same column order in the table.</exception>
        /// <exception cref="DbException">Thrown when the value size of some column in the column list exceeds the maximum value for the current dbms.</exception>
        /// <example>
        ///   The following example shows that two indexes with the same column set but different column order
        /// are considered to be different.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Creating a custom column
        ///             var column = table.AddColumn("my_column", ColumnType.Integer);
        /// 
        ///             //Adding two indexes
        ///             table.AddIndex(Guid.NewGuid(),
        ///                 table.SystemColumns.IsActive, column);
        ///             table.AddIndex(Guid.NewGuid(),
        ///                 column, table.SystemColumns.IsActive);
        ///             
        ///             //Enumerating all table indexes
        ///             foreach(var index in table.Indexes)
        ///             {
        ///                 Console.WriteLine("Index id = {0}, unique = {1} columns:", index.Id, index.IsUnique);
        /// 
        ///                 foreach(var c in index.Columns)
        ///                     Console.WriteLine("\tColumn name = {0}", c.Name);
        ///             }
        ///         }
        ///     }   
        /// </code>
        /// </example>
        public Index AddIndex(params Column[] indexedColumns)
        {
            return AddIndex(Guid.NewGuid(), indexedColumns);
        }

        /// <summary>
        ///   Deletes a table index with the specified id.
        /// </summary>
        /// <remarks>
        /// <para>The current user has to have the permission to change the schema in order to execute this function.</para>
        /// </remarks>
        /// <param name = "indexId">The id of the index.</param>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete default table indexes.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        /// 
        ///             //Deleting default table indexes
        ///             foreach(var index in table.Indexes)
        ///                 table.DeleteIndex(index.Id);
        /// 
        ///             //Inserting two with the same id
        ///             var id = Guid.NewGuid();
        /// 
        ///             table.InsertRow(id);
        ///             table.InsertRow(id);
        /// 
        ///             Console.WriteLine("Row count = {0}", table.RowCount);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteIndex(Guid indexId)
        {
            var indexToDelete = GetIndex(indexId);

            if (indexToDelete != null)
                indexToDelete.Delete();
        }

        /// <summary>
        ///   Deletes the specified table index.
        /// </summary>
        /// <remarks>
        /// <para>The current user has to have the permission to change the schema in order to execute this function.</para>
        /// </remarks>
        /// <param name = "index">The index to be deleted.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="index"/> is null.</exception>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to delete default table indexes.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        /// 
        ///             //Deleting default table indexes
        ///             foreach(var index in table.Indexes)
        ///                 table.DeleteIndex(index);
        /// 
        ///             //Inserting two with the same id
        ///             var id = Guid.NewGuid();
        /// 
        ///             table.InsertRow(id);
        ///             table.InsertRow(id);
        /// 
        ///             Console.WriteLine("Row count = {0}", table.RowCount);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void DeleteIndex(Index index)
        {
            if (index == null)
                throw new ArgumentNullException("index");

            DeleteIndex(index.Id);
        }

        /// <summary>
        ///   Retrieves a table index with the specified id.
        /// </summary>
        /// <param name = "indexId">The specified index id.</param>
        /// <returns>Returns a table index with the specified id if there is one; therwise, returns null.</returns>
        /// <example>
        ///   The following example shows how to retrieve a table index specifying the index id.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Enumerating default table indexes
        ///             foreach(var index in table.Indexes)
        ///             {
        ///                 var retrivedIndex = table.GetIndex(index.Id);
        /// 
        ///                 Console.WriteLine("Index id = {0}", retrivedIndex.Id);
        ///             }
        ///         }
        ///     }   
        /// </code>
        /// </example>
        public Index GetIndex(Guid indexId)
        {
            var indexWasFound = false;

            Domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select 1 from {0} where {1}=?",
                        Domain.GetObjectFullName(Db.Tables.IndexList.Name),
                        Db.Tables.IndexList.Col.IndexId);

                    cmd.Parameters.Add(
                        Domain.DbAccess.CreateParameter("index_id", OdbcType.Binary, indexId.ToByteArray()));
                },
                result => { indexWasFound = DbValue.GetInteger(result) == 1; });

            if (indexWasFound)
                return new Index(indexId, this);

            return null;
        }

        /// <summary>
        ///   Grants all table permissions to the current user.
        /// </summary>
        /// <remarks>
        /// <para>Executing this function grants full access select, insert, update and delete permission
        /// on the user view of the table to the current user.</para>
        /// <para>Table permissions should not duplicate each other. Two table permissions
        /// are considered to be duplicate if they have the same data action and the same security principal.</para>
        /// <para>The current user has to have the permission to change security settings in order to execute this function.</para>
        /// </remarks>
        /// <returns>Returns the list of granted table permissions.</returns>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <exception cref="DbException">Thrown when one of the privileges is already granted to the current user.</exception>
        /// <example>
        ///   The following example shows the permissions granted when executing this function. 
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             Console.WriteLine("Count of current user permissions = {0}",
        ///                 domain.CurrentUser.TablePermissions.Count());
        ///             
        ///             //Granting all permissions
        ///             Console.WriteLine("Granting the permissions");
        ///         
        ///             table.GrantAll();
        /// 
        ///             //Enumerating the granted permissions
        ///             foreach(var perm in domain.CurrentUser.TablePermissions)
        ///             {
        ///                 Console.WriteLine("Principal = {0}, action = {1}, level = {2}",
        ///                     perm.Principal.Name,
        ///                     perm.DataAction,
        ///                     perm.AccessLevel);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<TablePermission> GrantAll()
        {
            return GrantAll(Domain.CurrentUser);
        }

        /// <summary>
        ///   Grants all table permissions to the specified security principal.
        /// </summary>
        /// <remarks>
        /// <para>Security principal principal stands for a security role or a user.</para>
        /// <para>Table permissions should not duplicate each other. Two table permissions
        /// are considered to be duplicate if they have the same data action and the same security principal.</para>
        /// <para>The current user has to have the permission to change security settings in order to execute this function.</para>
        /// </remarks>
        /// <param name = "principal">The security principal.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="principal"/> is null.</exception>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <exception cref="DbException">Thrown when one of the privileges is already granted to the specified security principal.</exception>
        /// <returns>Returns the list of granted table permissions.</returns>
        /// <example>
        ///     The following example shows the permissions granted when executing this function.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Creating a new user
        ///             var newUser = domain.AddUser("new_user", "P@ssw0rd");
        /// 
        ///             Console.WriteLine("Count of the new user permissions = {0}",
        ///                 newUser.TablePermissions.Count());
        ///             
        ///             //Granting all permissions
        ///             Console.WriteLine("Granting table permissions to the new user");
        ///         
        ///             table.GrantAll(newUser);
        /// 
        ///             //Enumerating the granted permissions
        ///             foreach(var perm in newUser.TablePermissions)
        ///             {
        ///                 Console.WriteLine("Principal = {0}, action = {1}, level = {2}",
        ///                     perm.Principal.Name,
        ///                     perm.DataAction,
        ///                     perm.AccessLevel);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<TablePermission> GrantAll(IPrincipal principal)
        {
            if (principal == null)
                throw new ArgumentNullException("principal");

            return new List<TablePermission>
                       {
                           Grant(principal.Name, DataAction.Select),
                           Grant(principal.Name, DataAction.Insert),
                           Grant(principal.Name, DataAction.Update),
                           Grant(principal.Name, DataAction.Delete)
                       };
        }

        /// <summary>
        ///   Grants all table permissions to a security principal with the specified name.
        /// </summary>
        /// <remarks>
        /// <para>Security principal principal stands for a security role or a user.</para>
        /// <para>Table permissions should not duplicate each other. Two table permissions
        /// are considered to be duplicate if they have the same data action and the same security principal.</para>
        /// <para>The current user has to have the permission to change security settings in order to execute this function.</para>
        /// </remarks>
        /// <param name = "principalName">The name of the security principal.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="principalName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="principalName"/> is an empty string.</exception>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <exception cref="DbException">Thrown when one of the privileges is already granted to the specified security principal.</exception>
        /// <exception cref="DbException">Thrown when there is no security principal with the specified name.</exception>
        /// <returns>Returns the list of granted table permissions.</returns>
        /// <example>
        ///     The following example shows the permissions granted when executing this function.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Creating a new role
        ///             var newRole = domain.AddRole("new_role");
        /// 
        ///             Console.WriteLine("Count of the new user permissions = {0}",
        ///                 newRole.TablePermissions.Count());
        ///             
        ///             //Granting all permissions
        ///             Console.WriteLine("Granting table permissions to the new role");
        ///         
        ///             table.GrantAll(newRole.Name);
        /// 
        ///             //Enumerating the granted permissions
        ///             foreach(var perm in newRole.TablePermissions)
        ///             {
        ///                 Console.WriteLine("Principal = {0}, action = {1}, level = {2}",
        ///                     perm.Principal.Name,
        ///                     perm.DataAction,
        ///                     perm.AccessLevel);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<TablePermission> GrantAll(string principalName)
        {
            if (principalName == null)
                throw new ArgumentNullException("principalName");
            if (principalName.Trim().Length == 0)
                throw new ArgumentException("Principal name cannot be empty.");

            principalName = principalName.Trim();

            return new List<TablePermission>
                       {
                           Grant(principalName, DataAction.Select),
                           Grant(principalName, DataAction.Insert),
                           Grant(principalName, DataAction.Update),
                           Grant(principalName, DataAction.Delete)
                       };
        }

        /// <summary>
        ///   Grants a table permission of the specified type with the specified level to the current user.   
        /// </summary>
        /// <remarks>
        /// <para>Table permissions should not duplicate each other. Two table permissions
        /// are considered to be duplicate if they have the same data action and the same security principal.</para>
        /// <para>The current user has to have the permission to change security settings in order to execute this function.</para>
        /// </remarks>
        /// <param name = "dataAction">The data action to be permitted.</param>
        /// <param name = "accessLevel">The requested access level.</param>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <exception cref="DbException">Thrown when the specified permission was already granted to the current user.</exception>
        /// <returns>Returns the granted table permission.</returns>
        /// <example>
        ///   The following example shows how to grant the select unit permission to the current user.  
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Creating other users
        ///             var firstUser = domain.AddUser("first_user", "P@ssw0rd");
        ///             var secondUser = domain.AddUser("second_user", "P@ssw0rd");
        /// 
        ///             //Creating a business unit
        ///             var unit = domain.AddUnit("my_unit");
        /// 
        ///             //Adding two users into that unit
        ///             unit.AddUser(domain.CurrentUser);
        ///             unit.AddUser(firstUser);
        ///             //The second user is no added
        /// 
        ///             //Inserting a row owned by the first user
        ///             table.Grant(firstUser, DataAction.Insert);
        ///             domain.Logon("first_user", "P@ssw0rd").GetTable("my_table").InsertRow();
        /// 
        ///             //Inserting a row owned by the second user
        ///             table.Grant(secondUser, DataAction.Insert);
        ///             domain.Logon("second_user", "P@ssw0rd").GetTable("my_table").InsertRow();
        /// 
        ///             //Inserting a row owned by the current user
        ///             table.Grant(DataAction.Insert);
        ///             table.InsertRow();
        /// 
        ///             //Granting the select unit permission to the current user
        ///             table.Grant(DataAction.Select, AccessLevel.Unit);
        /// 
        ///             //Selecting the owners of the rows visible to the current user
        ///             domain.DbAccess.ExecuteReader(cmd =>
        ///                 {
        ///                 cmd.CommandText = string.Format("select id, owner from {0}", table.FullName);
        ///                 }, reader =>
        ///                 {
        ///                     Console.WriteLine("Row: id = {0}, owner = {1}",
        ///                         reader.GetGuid("id"), reader.GetString("owner"));
        ///                 });             
        ///         }
        ///     }
        /// </code>
        /// </example>
        public TablePermission Grant(DataAction dataAction, AccessLevel accessLevel = AccessLevel.All)
        {
            return Grant(Domain.CurrentUser.Name, dataAction, accessLevel);
        }

        /// <summary>
        ///   Grants a table permission of the specified type with the specified level to the specified security principal. 
        /// </summary>
        /// <remarks>
        /// <para>Security principal principal stands for a security role or a user.</para>
        /// <para>Table permissions should not duplicate each other. Two table permissions
        /// are considered to be duplicate if they have the same data action and the same security principal.</para>
        /// <para>The current user has to have the permission to change security settings in order to execute this function.</para>
        /// </remarks>
        /// <param name = "principal">The specified security principal.</param>
        /// <param name = "dataAction">The data action to be permitted.</param>
        /// <param name = "accessLevel">The requested access level.</param>
        /// <returns>Returns the granted table permission.</returns>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="principal"/> is null.</exception>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <exception cref="DbException">Thrown when the specified permission was already granted to the specified security principal.</exception>
        /// <example>
        ///   The following example shows how to grant the select unit permission to a security role.   
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Creating other users
        ///             var firstUser = domain.AddUser("first_user", "P@ssw0rd");
        ///             var secondUser = domain.AddUser("second_user", "P@ssw0rd");
        /// 
        ///             //Creating a business unit
        ///             var unit = domain.AddUnit("my_unit");
        /// 
        ///             //Adding two users into that unit
        ///             unit.AddUser(domain.CurrentUser);
        ///             unit.AddUser(firstUser);
        ///             //The second user is no added
        /// 
        ///             //Inserting a row owned by the first user
        ///             table.Grant(firstUser, DataAction.Insert);
        ///             domain.Logon("first_user", "P@ssw0rd").GetTable("my_table").InsertRow();
        /// 
        ///             //Inserting a row owned by the second user
        ///             table.Grant(secondUser, DataAction.Insert);
        ///             domain.Logon("second_user", "P@ssw0rd").GetTable("my_table").InsertRow();
        /// 
        ///             //Inserting a row owned by the current user
        ///             table.Grant(DataAction.Insert);
        ///             table.InsertRow();
        /// 
        ///             //Creating a security role
        ///             var role = domain.AddRole("my_role");
        /// 
        ///             //Granting the select unit permission to that security role
        ///             table.Grant(role, DataAction.Select, AccessLevel.Unit);
        /// 
        ///             //Adding the current user to that role
        ///             role.AddUser(domain.CurrentUser);
        /// 
        ///             //Selecting the owners of the rows visible to the current user
        ///             domain.DbAccess.ExecuteReader(cmd =>
        ///                 {
        ///                 cmd.CommandText = string.Format("select id, owner from {0}", table.FullName);
        ///                 }, reader =>
        ///                 {
        ///                     Console.WriteLine("Row: id = {0}, owner = {1}",
        ///                         reader.GetGuid("id"), reader.GetString("owner"));
        ///                 });             
        ///         }
        ///     }
        /// </code>
        /// </example>
        public TablePermission Grant(IPrincipal principal, DataAction dataAction, AccessLevel accessLevel = AccessLevel.All)
        {
            if (principal == null)
                throw new ArgumentNullException("principal");

            return Grant(principal.Name, dataAction, accessLevel);
        }

        /// <summary>
        ///    Grants a table permission of the specified type with the specified level to a security principal with the specified name.
        /// </summary>
        /// <remarks>
        /// <para>Security principal principal stands for a security role or a user.</para>
        /// <para>Table permissions should not duplicate each other. Two table permissions
        /// are considered to be duplicate if they have the same data action and the same security principal.</para>
        /// <para>The current user has to have the permission to change security settings in order to execute this function.</para>
        /// </remarks>
        /// <param name = "principalName">The name of the security principal.</param>
        /// <param name = "dataAction">The data action to be permitted.</param>
        /// <param name = "accessLevel">The requested access level.</param>
        /// <returns>Returns the granted table permission.</returns>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="principalName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="principalName"/> is an empty string.</exception>
        /// <exception cref="DbException">Thrown when there is no security principal with the specified name.</exception>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <exception cref="DbException">Thrown when the specified permission was already granted to the specified security principal.</exception>
        /// <example>
        ///   The following example shows how to grant the select unit permission to a security role.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Creating other users
        ///             var firstUser = domain.AddUser("first_user", "P@ssw0rd");
        ///             var secondUser = domain.AddUser("second_user", "P@ssw0rd");
        /// 
        ///             //Creating a business unit
        ///             var unit = domain.AddUnit("my_unit");
        /// 
        ///             //Adding two users into that unit
        ///             unit.AddUser(domain.CurrentUser);
        ///             unit.AddUser(firstUser);
        ///             //The second user is no added
        /// 
        ///             //Inserting a row owned by the first user
        ///             table.Grant(firstUser, DataAction.Insert);
        ///             domain.Logon("first_user", "P@ssw0rd").GetTable("my_table").InsertRow();
        /// 
        ///             //Inserting a row owned by the second user
        ///             table.Grant(secondUser, DataAction.Insert);
        ///             domain.Logon("second_user", "P@ssw0rd").GetTable("my_table").InsertRow();
        /// 
        ///             //Inserting a row owned by the current user
        ///             table.Grant(DataAction.Insert);
        ///             table.InsertRow();
        /// 
        ///             //Creating a security role
        ///             var role = domain.AddRole("my_role");
        /// 
        ///             //Granting the select unit permission to that security role
        ///             table.Grant(role.Name, DataAction.Select, AccessLevel.Unit);
        /// 
        ///             //Adding the current user to that role
        ///             role.AddUser(domain.CurrentUser);
        /// 
        ///             //Selecting the owners of the rows visible to the current user
        ///             domain.DbAccess.ExecuteReader(cmd =>
        ///                 {
        ///                 cmd.CommandText = string.Format("select id, owner from {0}", table.FullName);
        ///                 }, reader =>
        ///                 {
        ///                     Console.WriteLine("Row: id = {0}, owner = {1}",
        ///                         reader.GetGuid("id"), reader.GetString("owner"));
        ///                 });             
        ///         }
        ///     }
        /// </code>
        /// </example>
        public TablePermission Grant(string principalName, DataAction dataAction, AccessLevel accessLevel = AccessLevel.All)
        {
            if (principalName == null)
                throw new ArgumentNullException("principalName");
            if (principalName.Trim().Length == 0)
                throw new ArgumentException("Principal name cannot be empty.");

            principalName = principalName.Trim();

            //Principal name is lowered inside the stored procedure
            
            Domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format("{{call {0}(?,?,?,?)}}",
                        Domain.GetObjectFullName(Db.Procedures.GrantTablePermission));

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_table_id",
                        OdbcType.Binary, GetRawId()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_principal_name",
                        OdbcType.NVarChar, principalName));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_access_type",
                        OdbcType.Int, (int)dataAction));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_access_level",
                        OdbcType.Int, (int)accessLevel));
                });

            return new TablePermission(this, principalName, dataAction);
        }

        /// <summary>
        ///   Gets the table permissions granted to the specified security principal.
        /// </summary>
        /// <remarks>
        /// <para>The current user has to have the permission to change security settings in order to execute this function.</para>
        /// </remarks>
        /// <param name = "principal">The specified security principal.</param>
        /// <returns>Returns the list of the table permissions granted to the specified security principal.</returns>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="principal"/> is null.</exception>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to get the permissions granted to the current user.   
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Granting permissions to the current user
        ///             table.GrantAll();
        /// 
        ///             //Getting the permissions granted to the current user
        ///             foreach(var perm in table.GetPrincipalPermissions(domain.CurrentUser))
        ///             {
        ///                 Console.WriteLine("Principal = {0}, action = {1}, level = {2}",
        ///                     perm.Principal.Name,
        ///                     perm.DataAction,
        ///                     perm.AccessLevel);
        ///             }
        ///         }
        ///     }   
        /// </code>
        /// </example>
        public ICollection<TablePermission> GetPrincipalPermissions(IPrincipal principal)
        {
            if (principal == null)
                throw new ArgumentNullException("principal");

            return GetPrincipalPermissions(principal.Name);
        }

        /// <summary>
        ///   Gets the table permissions granted to a security principal with the specified name.
        /// </summary>
        /// <remarks>
        /// <para>The current user has to have the permission to change security settings in order to execute this function.</para>
        /// </remarks>
        /// <param name = "principalName">The specified principal name.</param>
        /// <returns>Returns the list of the table permissions granted to a security principal with the specified name.</returns>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="principalName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="principalName"/> is an empty string.</exception>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to get the permissions granted to the current user.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Granting permissions to the current user
        ///             table.GrantAll();
        /// 
        ///             //Getting the permissions granted to the current user
        ///             foreach(var perm in table.GetPrincipalPermissions(domain.CurrentUser.Name))
        ///             {
        ///                 Console.WriteLine("Principal = {0}, action = {1}, level = {2}",
        ///                     perm.Principal.Name,
        ///                     perm.DataAction,
        ///                     perm.AccessLevel);
        ///             }   
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<TablePermission> GetPrincipalPermissions(string principalName)
        {
            if (principalName == null)
                throw new ArgumentNullException("principalName");
            if (principalName.Trim().Length == 0)
                throw new ArgumentException("Principal name cannot be empty.");

            principalName = principalName.Trim();

            var resultList = new List<TablePermission>();

            Domain.DbAccess.ExecuteReader(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select {0} from {1} where {2}=? and {3}=?",
                        Db.Tables.TableAcl.Col.DataAction,
                        Domain.GetObjectFullName(Db.Tables.TableAcl.Name),
                        Db.Tables.TableAcl.Col.TableId,
                        Db.Tables.TableAcl.Col.PrincipalName);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, GetRawId()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("principal_name", OdbcType.NVarChar, principalName));
                },
                reader =>
                {
                    var dataActionValue = DbValue.GetInteger(reader[Db.Tables.TableAcl.Col.DataAction])
                        .GetValueOrDefault();

                    var dataAction = (DataAction)dataActionValue;

                    resultList.Add(new TablePermission(this, principalName, dataAction));
                });

            return resultList;
        }

        /// <summary>
        ///   Revokes the specified table permission.
        /// </summary>
        /// <remarks>
        /// <para>The current user has to have the permission to change security settings in order to execute this function.</para>
        /// </remarks>
        /// <param name = "permission">The specified table permission.</param>
        /// <exception cref="DbException">Thrown when the value of <paramref name="permission"/> is null.</exception>
        /// <exception cref="DbException">Thrown when the specified permission was not granted on the table.</exception>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows granting an revoking the insert permission.
        /// <code>
        ///     using System;
        ///     using System.Data.Common;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Granting the insert permission to the current user
        ///             var insertPerm = table.Grant(DataAction.Insert);
        /// 
        ///             Console.WriteLine("Granting the insert permission");
        /// 
        ///             //Inserting a row
        ///             table.InsertRow();
        /// 
        ///             Console.WriteLine("The row was inserted");
        /// 
        ///             //Revoking the insert permission
        ///             table.Revoke(insertPerm);
        /// 
        ///             Console.WriteLine("Revoking the insert permission");
        /// 
        ///             try
        ///             {
        ///                 table.InsertRow();
        ///             }
        ///             catch(DbException)
        ///             {
        ///                 Console.WriteLine("Row inserting failed");
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public void Revoke(TablePermission permission)
        {
            if (permission == null)
                throw new ArgumentNullException("permission");

            permission.Delete();
        }

        /// <summary>
        ///   Revokes all permissions granted on the table.
        /// </summary>
        /// <remarks>
        /// <para>The current user has to have the permission to change security settings in order to execute this function.</para>
        /// </remarks>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to revoke all permission granted on a table.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Granting permissions to the current user
        ///             table.GrantAll();
        ///             
        ///             //Creating a role
        ///             var role = domain.AddRole("my_role");
        /// 
        ///             //Granting permissions to the role
        ///             table.GrantAll(role);
        /// 
        ///             Console.WriteLine("Table permissions: count = {0}", table.Permissions.Count());
        /// 
        ///             //Revoking all permissions
        ///             table.RevokeAll();
        /// 
        ///             Console.WriteLine("Revoking all permissions");
        /// 
        ///             Console.WriteLine("Table permissions: count = {0}", table.Permissions.Count());
        ///         }
        ///     }   
        /// </code>
        /// </example>
        public void RevokeAll()
        {
            foreach (var perm in Permissions)
                perm.Delete();
        }

        /// <summary>
        ///   Revokes all table permissions granted a security principal with the specified name.
        /// </summary>
        /// <remarks>
        /// <para>The current user has to have the permission to change security settings in order to execute this function.</para>
        /// </remarks>
        /// <param name="principalName">The name of the security principal.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="principalName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="principalName"/> is an empty string.</exception>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to revoke table permissions granted to the current user.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Granting permissions to the current user
        ///             table.GrantAll();
        ///             
        ///             //Creating a role
        ///             var role = domain.AddRole("my_role");
        /// 
        ///             //Granting permissions to the role
        ///             table.GrantAll(role);
        /// 
        ///             //Enumerating the granted permissions
        ///             foreach(var perm in table.Permissions)
        ///             {
        ///                 Console.WriteLine("Principal = {0}, action = {1}, level = {2}",
        ///                     perm.Principal.Name,
        ///                     perm.DataAction,
        ///                     perm.AccessLevel);
        ///             }
        /// 
        ///             //Revoking all permissions
        ///             table.RevokeAll(domain.CurrentUser.Name);
        /// 
        ///             Console.WriteLine("Revoking all permissions");
        /// 
        ///             foreach(var perm in table.Permissions)
        ///             {
        ///                 Console.WriteLine("Principal = {0}, action = {1}, level = {2}",
        ///                     perm.Principal.Name,
        ///                     perm.DataAction,
        ///                     perm.AccessLevel);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void RevokeAll(string principalName)
        {
            if (principalName == null)
                throw new ArgumentNullException("principalName");
            if (principalName.Trim().Length == 0)
                throw new ArgumentException("Principal name cannot be empty.");

            principalName = principalName.Trim().ToLower();

            foreach (var perm in Permissions.Where(p => p.Principal.Name == principalName))
                perm.Delete();
        }

        /// <summary>
        ///   Revokes all table permissions granted to the specified security principal.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the permission to change security settings in order to execute this function.
        /// </remarks>
        /// <param name="principal">The security principal.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="principal"/> is null.</exception>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///    The following example shows how to revoke table permissions granted to the current user.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Granting permissions to the current user
        ///             table.GrantAll();
        ///             
        ///             //Creating a role
        ///             var role = domain.AddRole("my_role");
        /// 
        ///             //Granting permissions to the role
        ///             table.GrantAll(role);
        /// 
        ///             //Enumerating the granted permissions
        ///             foreach(var perm in table.Permissions)
        ///             {
        ///                 Console.WriteLine("Principal = {0}, action = {1}, level = {2}",
        ///                     perm.Principal.Name,
        ///                     perm.DataAction,
        ///                     perm.AccessLevel);
        ///             }
        /// 
        ///             //Revoking all permissions
        ///             table.RevokeAll(domain.CurrentUser);
        /// 
        ///             Console.WriteLine("Revoking all permissions");
        /// 
        ///             foreach(var perm in table.Permissions)
        ///             {
        ///                 Console.WriteLine("Principal = {0}, action = {1}, level = {2}",
        ///                     perm.Principal.Name,
        ///                     perm.DataAction,
        ///                     perm.AccessLevel);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void RevokeAll(IPrincipal principal)
        {
            if (principal == null)
                throw new ArgumentNullException("principal");

            RevokeAll(principal.Name);
        }


        /// <summary>
        ///   Revokes all table permissions of the specified data action.
        /// </summary>
        /// <remarks>The current user has to have the permission to change security settings in order to execute this function.</remarks>
        /// <param name="dataAction">The data action of the permissions to be deleted.</param>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to revoke all table permissions of the specified data action.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Granting permissions to the current user
        ///             table.GrantAll();
        ///             
        ///             //Creating a role
        ///             var role = domain.AddRole("my_role");
        /// 
        ///             //Granting permissions to the role
        ///             table.GrantAll(role);
        /// 
        ///             //Enumerating the granted permissions
        ///             foreach(var perm in table.Permissions)
        ///             {
        ///                 Console.WriteLine("Principal = {0}, action = {1}, level = {2}",
        ///                     perm.Principal.Name,
        ///                     perm.DataAction,
        ///                     perm.AccessLevel);
        ///             }
        /// 
        ///             //Revoking all permissions of the select data action
        ///             table.RevokeAll(DataAction.Select);
        /// 
        ///             Console.WriteLine("Revoking all permissions of the select data action");
        /// 
        ///             foreach(var perm in table.Permissions)
        ///             {
        ///                 Console.WriteLine("Principal = {0}, action = {1}, level = {2}",
        ///                     perm.Principal.Name,
        ///                     perm.DataAction,
        ///                     perm.AccessLevel);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void RevokeAll(DataAction dataAction)
        {
            foreach (var perm in Permissions.Where(p => p.DataAction == dataAction))
                perm.Delete();
        }


        /// <summary>
        ///   Revokes all table permissions of the specified data action and of the principal with the specified name.
        /// </summary>
        /// <remarks>The current user has to have the permission to change security settings in order to execute this function.</remarks>
        /// <param name="dataAction">The data action of the permissions to be deleted.</param>
        /// <param name="principalName">The principal name of the permissions to be deleted.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="principalName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="principalName"/> is an empty string.</exception>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to revoke all table permissions of the specified data action and of the principal with the specified name.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Granting permissions to the current user
        ///             table.GrantAll();
        ///             
        ///             //Creating a role
        ///             var role = domain.AddRole("my_role");
        /// 
        ///             //Granting permissions to the role
        ///             table.GrantAll(role);
        /// 
        ///             //Enumerating the granted permissions
        ///             foreach(var perm in table.Permissions)
        ///             {
        ///                 Console.WriteLine("Principal = {0}, action = {1}, level = {2}",
        ///                     perm.Principal.Name,
        ///                     perm.DataAction,
        ///                     perm.AccessLevel);
        ///             }
        /// 
        ///             //Revoking all permissions of the select data action
        ///             table.RevokeAll(DataAction.Select, role.Name);
        /// 
        ///             Console.WriteLine("Revoking all permissions of the select data action and of the principal with the specified name");
        /// 
        ///             foreach(var perm in table.Permissions)
        ///             {
        ///                 Console.WriteLine("Principal = {0}, action = {1}, level = {2}",
        ///                     perm.Principal.Name,
        ///                     perm.DataAction,
        ///                     perm.AccessLevel);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void RevokeAll(DataAction dataAction, string principalName)
        {
            if (principalName == null)
                throw new ArgumentNullException("principalName");
            if (principalName.Trim().Length == 0)
                throw new ArgumentException("The principal name cannot be empty");

            principalName = principalName.Trim();

            foreach (var perm in Permissions.Where(p => p.DataAction == dataAction && p.Principal.Name == principalName))
                perm.Delete();

        }


        /// <summary>
        ///   Revokes all table permissions of the specified data action and of the specified principal.
        /// </summary>
        /// <remarks>The current user has to have the permission to change security settings in order to execute this function.</remarks>
        /// <param name="dataAction">The data action of the permissions to be deleted.</param>
        /// <param name="principal">The principal of the permissions to be deleted.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="principal"/> is null.</exception>
        /// <exception cref="DbException">Thrown when the current user has no permission to execute this function.</exception>
        /// <example>
        ///   The following example shows how to revoke all table permissions of the specified data action and of the specified principal.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             
        ///             //Granting permissions to the current user
        ///             table.GrantAll();
        ///             
        ///             //Creating a role
        ///             var role = domain.AddRole("my_role");
        /// 
        ///             //Granting permissions to the role
        ///             table.GrantAll(role);
        /// 
        ///             //Enumerating the granted permissions
        ///             foreach(var perm in table.Permissions)
        ///             {
        ///                 Console.WriteLine("Principal = {0}, action = {1}, level = {2}",
        ///                     perm.Principal.Name,
        ///                     perm.DataAction,
        ///                     perm.AccessLevel);
        ///             }
        /// 
        ///             //Revoking all permissions of the select data action
        ///             table.RevokeAll(DataAction.Select, role);
        /// 
        ///             Console.WriteLine("Revoking all permissions of the select data action and of the principal with the specified name");
        /// 
        ///             foreach(var perm in table.Permissions)
        ///             {
        ///                 Console.WriteLine("Principal = {0}, action = {1}, level = {2}",
        ///                     perm.Principal.Name,
        ///                     perm.DataAction,
        ///                     perm.AccessLevel);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void RevokeAll(DataAction dataAction, IPrincipal principal)
        {
            if (principal == null)
                throw new ArgumentNullException("principal");

            foreach (var perm in Permissions.Where(p => p.DataAction == dataAction && p.Principal.Name == principal.Name))
                perm.Delete();
        }

        /// <summary>
        ///   Gets or sets the availability of unit level security for the table.
        /// </summary>
        /// <remarks>
        /// <para>Turning unit level security off means limiting the scope
        /// of table permissions. After that each permission is considered as granted
        /// on all rows of the table or none rows at all. All permissions on user
        /// rows, unit rows etc. are considered as permissions on all rows.</para>
        /// <para>The current user has to have the permission to change security
        /// settings in order to set this property.</para>
        /// </remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user
        /// has no permission to set this property.</exception>
        /// <example>
        ///   The following example shows how to turn unit level permissions off.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        ///             table.GrantAll();
        /// 
        ///             //Creating another user
        ///             var user = domain.AddUser("my_user", "P@ssw0rd");
        /// 
        ///             //Granting the user permission to select only his rows
        ///             table.Grant(user, DataAction.Select, AccessLevel.Own);
        ///             table.Grant(user, DataAction.Insert);
        /// 
        ///             var userTable = domain.Logon("my_user", "P@ssw0rd").GetTable("my_table");
        /// 
        ///             //Inserting a row as the current user
        ///             table.InsertRow();
        /// 
        ///             //Inserting a row as the user
        ///             userTable.InsertRow();
        /// 
        ///             Console.WriteLine("User: table row count = {0}", userTable.RowCount);   
        /// 
        ///             Console.WriteLine("Turning unit level security off");
        ///             
        ///             table.HasUnitLevelSecurity = false;
        /// 
        ///             Console.WriteLine("User: table row count = {0}", userTable.RowCount);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public bool HasUnitLevelSecurity
        {
            get { return DbValue.GetBoolean(ReadProperty(Db.Tables.TableList.Col.HasUnitLevelSecurity)).GetValueOrDefault(); }
            set
            {
                Domain.DbAccess.ExecuteNonQuery(cmd =>
                    {
                        cmd.CommandText = string.Format("{{call {0}(?,?)}}",
                            Domain.GetObjectFullName(Db.Procedures.SetTableUnitLevelSecurity));

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_table_id",
                            OdbcType.Binary, GetRawId()));
                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_unit_level_security",
                            OdbcType.Int, value ? 1 : 0));
                    });
            }
        }

        /// <summary>
        ///   Reads the table property.
        /// </summary>
        /// <param name = "propertyName">The property name.</param>
        /// <returns>Returns the property value.</returns>
        private object ReadProperty(string propertyName)
        {
            object propertyValue = null;

            _domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select {0} from {1} where {2}=?",
                        propertyName,
                        Domain.GetObjectFullName(Db.Tables.TableList.Name),
                        Db.Tables.TableList.Col.TableId);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, GetRawId()));
                },
                result => propertyValue = result);

            return propertyValue;
        }

        /// <summary>
        ///   Writes the table property.
        /// </summary>
        /// <param name = "propertyName">The property name.</param>
        /// <param name = "propertyValue">The property value.</param>
        /// <param name = "propertyType">The property database type.</param>
        private void WriteProperty(string propertyName, object propertyValue, OdbcType propertyType)
        {
            var rowCount = _domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "update {0} set {1}=? where {2}=?",
                        Domain.GetObjectFullName(Db.Tables.TableList.Name),
                        propertyName,
                        Db.Tables.TableList.Col.TableId);

                    cmd.Parameters.Add(
                        Domain.DbAccess.CreateParameter("value", propertyType, DbValue.GetDbValue(propertyValue)));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, GetRawId()));
                });

            if (rowCount < 1)
                throw new DbOperationException();
        }

        /// <summary>
        ///   Creates an instance of the table column.
        /// </summary>
        /// <param name = "columnId">The id of the column.</param>
        /// <param name = "columnTableName">The name of the parent table.</param>
        /// <param name="columnName">The column name.</param>
        /// <param name="displayName">The column display name.</param>
        /// <param name="description">The column description.</param>
        /// <param name="isNotNull">The column ability to hold null values.</param>
        /// <param name="orderIndex">The column order index</param>
        /// <param name="customizationlevel">The column customization level.</param>
        /// <returns>Returns an instance of the table column.</returns>
        private Column CreateColumn(byte[] columnId, string columnTableName,
            string columnName, string displayName, string description,
            bool isNotNull, int orderIndex, int customizationlevel)
        {
            var columnGuid = new Guid(columnId);

            switch (columnTableName)
            {
                case Db.Tables.StringColumnList.Name:
                    return new StringColumn(columnGuid, this, columnName, displayName,
                        description, isNotNull, orderIndex, customizationlevel);
                case Db.Tables.ReferenceColumnList.Name:
                    return new RefColumn(columnGuid, this, columnName, displayName,
                        description, isNotNull, orderIndex, customizationlevel);
                case Db.Tables.BigIntegerColumnList.Name:
                    return new BigIntColumn(columnGuid, this, columnName, displayName,
                        description, isNotNull, orderIndex, customizationlevel);
                case Db.Tables.BinaryColumnList.Name:
                    return new BinaryColumn(columnGuid, this, columnName, displayName,
                        description, isNotNull, orderIndex, customizationlevel);
                case Db.Tables.BooleanColumnList.Name:
                    return new BoolColumn(columnGuid, this, columnName, displayName,
                        description, isNotNull, orderIndex, customizationlevel);
                case Db.Tables.DateTimeColumnList.Name:
                    return new DateTimeColumn(columnGuid, this, columnName, displayName,
                        description, isNotNull, orderIndex, customizationlevel);
                case Db.Tables.EnumerationColumnList.Name:
                    return new EnumColumn(columnGuid, this, columnName, displayName,
                        description, isNotNull, orderIndex, customizationlevel);
                case Db.Tables.IntegerColumnList.Name:
                    return new IntColumn(columnGuid, this, columnName, displayName,
                        description, isNotNull, orderIndex, customizationlevel);
                case Db.Tables.DecimalColumnList.Name:
                    return new DecimalColumn(columnGuid, this, columnName, displayName,
                        description, isNotNull, orderIndex, customizationlevel);
                case Db.Tables.SmallIntegerColumnList.Name:
                    return new SmallIntColumn(columnGuid, this, columnName, displayName,
                        description, isNotNull, orderIndex, customizationlevel);
                case Db.Tables.GuidColumnList.Name:
                    return new GuidColumn(columnGuid, this, columnName, displayName,
                        description, isNotNull, orderIndex, customizationlevel);
                case Db.Tables.TextColumnList.Name:
                    return new TextColumn(columnGuid, this, columnName, displayName,
                        description, isNotNull, orderIndex, customizationlevel);
                default:
                    throw new NotSupportedException();
            }
        }

        /// <summary>
        ///   Gets a table by an id of one of the table columns.
        /// </summary>
        /// <param name = "columnId">The column id.</param>
        /// <returns>Returns a table that contains the column if such a table exists; otherwise; null.</returns>
        private Table GetTableByColumnId(Guid columnId)
        {
            Table table = null;

            Domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select {0} from {1} where {2}=?",
                        Db.Views.ColumnList.Col.TableId,
                        Domain.GetObjectFullName(Db.Views.ColumnList.Name),
                        Db.Views.ColumnList.Col.ColumnId);

                    cmd.Parameters.Add(
                        Domain.DbAccess.CreateParameter("column_id", OdbcType.Binary, columnId.ToByteArray()));
                },
                reader =>
                {
                    var tableId = DbValue.GetGuid(reader).GetValueOrDefault();

                    table = new Table(tableId, Domain);
                });

            return table;
        }

        /// <summary>
        ///   Gets the name of the specified data source.
        /// </summary>
        /// <param name="source">The specified data source.</param>
        /// <returns>Returns the name of the data source.</returns>
        internal string GetDataSourceName(DataSource source)
        {
            switch (source)
            {
                case DataSource.InternalTable:
                    return FullInternalName;
                case DataSource.UserView:
                    return FullName;
                default:
                    throw new NotSupportedException();
            }
        }


        /// <summary>
        ///   Refreshes the properties of the table.
        /// </summary>
        public void Refresh()
        {
            _name = null;
            _displayName = null;
            _description = null;
        }


        /// <summary>
        ///   Clears the table column cache.
        /// </summary>
        private void ClearColumnCache()
        {
            _columnsById.Clear();
            _columnsByName.Clear();
        }


        /// <summary>
        ///   Gets a table column with the specified id from the column cache.
        /// </summary>
        /// <param name="columnId">The target column id.</param>
        /// <returns>Returns a column with the specified id from the column cache
        /// if there is one; otherwise, returns null.</returns>
        private Column GetColumnFromCache(Guid columnId)
        {
            return !_columnsById.ContainsKey(columnId) ? null : _columnsById[columnId];
        }


        /// <summary>
        ///   Gets a column with the specified name from the column cache.
        /// </summary>
        /// <param name="columnName">The target column name.</param>
        /// <returns>Returns a column with the specified name from the column cache
        /// if there is one; otherwise, returns null.</returns>
        private Column GetColumnFromCache(string columnName)
        {
            return !_columnsByName.ContainsKey(columnName) ? null : _columnsByName[columnName];
        }


        /// <summary>
        ///   Saves the specified table column to the column cache.
        /// </summary>
        /// <param name="column">The target table column.</param>
        private void SaveColumnToCache(Column column)
        {
            if (!_columnsById.ContainsKey(column.Id))
                _columnsById.Add(column.Id, column);

            if (!_columnsByName.ContainsKey(column.Name))
                _columnsByName.Add(column.Name, column);
        }


        /// <summary>
        ///   Deletes the specified column from the column cache.
        /// </summary>
        /// <param name="column">The column to be deleted from the cache.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="column"/> is null.</exception>
        internal void DeleteColumnFromCache(Column column)
        {
            if (column == null)
                throw new ArgumentNullException("column");

            if (_columnsById.ContainsKey(column.Id))
                _columnsById.Remove(column.Id);

            if (_columnsByName.ContainsKey(column.Name))
                _columnsByName.Remove(column.Name);
        }
    }
}